mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-15 22:34:47 +02:00
Compare commits
15 Commits
codegen-v0
...
web-v4.0.0
Author | SHA1 | Date | |
---|---|---|---|
|
455d5c460d | ||
|
8faca783fa | ||
|
edbb9b047e | ||
|
32742d0715 | ||
|
d90c1a2331 | ||
|
2a12b41456 | ||
|
6c97d448b7 | ||
|
c3ce33df05 | ||
|
4431c8da65 | ||
|
2d11ab5977 | ||
|
4ebf16890d | ||
|
fe0bbfb3da | ||
|
2462b6dd5d | ||
|
49cfabeaf5 | ||
|
0f7292c69a |
20
CHANGES.md
20
CHANGES.md
@@ -3,6 +3,26 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.20 - 2022-01-14
|
||||||
|
### Added
|
||||||
|
- `GuardContext::header` [#2569]
|
||||||
|
- `ServiceConfig::configure` to allow easy nesting of configuration functions. [#1988]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `HttpResponse` can now be used as a `Responder` with any body type. [#2567]
|
||||||
|
- `Result` extractor wrapper can now convert error types. [#2581]
|
||||||
|
- Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581]
|
||||||
|
- Maximum number of handler extractors has increased to 12. [#2582]
|
||||||
|
- Removed bound `<B as MessageBody>::Error: Debug` in test utility functions in order to support returning opaque apps. [#2584]
|
||||||
|
|
||||||
|
[#1988]: https://github.com/actix/actix-web/pull/1988
|
||||||
|
[#2567]: https://github.com/actix/actix-web/pull/2567
|
||||||
|
[#2569]: https://github.com/actix/actix-web/pull/2569
|
||||||
|
[#2581]: https://github.com/actix/actix-web/pull/2581
|
||||||
|
[#2582]: https://github.com/actix/actix-web/pull/2582
|
||||||
|
[#2584]: https://github.com/actix/actix-web/pull/2584
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.19 - 2022-01-04
|
## 4.0.0-beta.19 - 2022-01-04
|
||||||
### Added
|
### Added
|
||||||
- `impl Hash` for `http::header::Encoding`. [#2501]
|
- `impl Hash` for `http::header::Encoding`. [#2501]
|
||||||
|
11
Cargo.toml
11
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.19"
|
version = "4.0.0-beta.20"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
@@ -71,14 +71,14 @@ experimental-io-uring = ["actix-server/io-uring"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-macros = "0.2.3"
|
actix-macros = "0.2.3"
|
||||||
actix-rt = "2.3"
|
actix-rt = "2.6"
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2.0.0-rc.4"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-beta.18"
|
||||||
actix-router = "0.5.0-beta.4"
|
actix-router = "0.5.0-rc.1"
|
||||||
actix-web-codegen = "0.5.0-rc.1"
|
actix-web-codegen = "0.5.0-rc.1"
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
@@ -105,7 +105,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
|||||||
url = "2.1"
|
url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-files = "0.6.0-beta.13"
|
actix-files = "0.6.0-beta.14"
|
||||||
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
|
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
|
||||||
|
|
||||||
@@ -118,6 +118,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"]
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
|
static_assertions = "1"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||||
zstd = "0.9"
|
zstd = "0.9"
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.0.0-beta.19)
|
[](https://docs.rs/actix-web/4.0.0-beta.20)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.19)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.20)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||

|

|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@@ -3,6 +3,12 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.14 - 2022-01-14
|
||||||
|
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
|
||||||
|
|
||||||
|
[#2583]: https://github.com/actix/actix-web/pull/2583
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.13 - 2022-01-04
|
## 0.6.0-beta.13 - 2022-01-04
|
||||||
- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398]
|
- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398]
|
||||||
- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
|
- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.13"
|
version = "0.6.0-beta.14"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
|||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-beta.18"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-beta.19", default-features = false }
|
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
@@ -39,10 +39,10 @@ mime_guess = "2.0.1"
|
|||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project-lite = "0.2.7"
|
pin-project-lite = "0.2.7"
|
||||||
|
|
||||||
tokio-uring = { version = "0.1", optional = true }
|
tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.11"
|
actix-test = "0.1.0-beta.11"
|
||||||
actix-web = "4.0.0-beta.19"
|
actix-web = "4.0.0-beta.20"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.0-beta.13)
|
[](https://docs.rs/actix-files/0.6.0-beta.14)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.13)
|
[](https://deps.rs/crate/actix-files/0.6.0-beta.14)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@@ -10,6 +10,9 @@ use actix_web::{error::Error, web::Bytes};
|
|||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
#[cfg(feature = "experimental-io-uring")]
|
||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use super::named::File;
|
use super::named::File;
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
@@ -214,64 +217,3 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
|
||||||
use bytes_mut::BytesMut;
|
|
||||||
|
|
||||||
// TODO: remove new type and use bytes::BytesMut directly
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
|
||||||
mod bytes_mut {
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use tokio_uring::buf::{IoBuf, IoBufMut};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BytesMut(bytes::BytesMut);
|
|
||||||
|
|
||||||
impl BytesMut {
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self(bytes::BytesMut::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for BytesMut {
|
|
||||||
type Target = bytes::BytesMut;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for BytesMut {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl IoBuf for BytesMut {
|
|
||||||
fn stable_ptr(&self) -> *const u8 {
|
|
||||||
self.0.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bytes_init(&self) -> usize {
|
|
||||||
self.0.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bytes_total(&self) -> usize {
|
|
||||||
self.0.capacity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl IoBufMut for BytesMut {
|
|
||||||
fn stable_mut_ptr(&mut self) -> *mut u8 {
|
|
||||||
self.0.as_mut_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn set_init(&mut self, init_len: usize) {
|
|
||||||
if self.len() < init_len {
|
|
||||||
self.0.set_len(init_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -67,8 +67,8 @@ mod tests {
|
|||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_service::ServiceFactory;
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
dev::ServiceFactory,
|
||||||
guard,
|
guard,
|
||||||
http::{
|
http::{
|
||||||
header::{self, ContentDisposition, DispositionParam, DispositionType},
|
header::{self, ContentDisposition, DispositionParam, DispositionType},
|
||||||
@@ -303,7 +303,7 @@ mod tests {
|
|||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"application/javascript"
|
"application/javascript; charset=utf-8"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt,
|
|
||||||
fs::Metadata,
|
fs::Metadata,
|
||||||
io,
|
io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::{self, BoxBody, SizedStream},
|
body::{self, BoxBody, SizedStream},
|
||||||
dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
|
dev::{
|
||||||
|
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory,
|
||||||
|
ServiceRequest, ServiceResponse,
|
||||||
|
},
|
||||||
http::{
|
http::{
|
||||||
header::{
|
header::{
|
||||||
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
|
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
|
||||||
@@ -37,7 +38,7 @@ bitflags! {
|
|||||||
|
|
||||||
impl Default for Flags {
|
impl Default for Flags {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Flags::from_bits_truncate(0b0000_0111)
|
Flags::from_bits_truncate(0b0000_1111)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +66,12 @@ impl Default for Flags {
|
|||||||
/// NamedFile::open_async("./static/index.html").await
|
/// NamedFile::open_async("./static/index.html").await
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Debug, Deref, DerefMut)]
|
||||||
pub struct NamedFile {
|
pub struct NamedFile {
|
||||||
path: PathBuf,
|
|
||||||
#[deref]
|
#[deref]
|
||||||
#[deref_mut]
|
#[deref_mut]
|
||||||
file: File,
|
file: File,
|
||||||
|
path: PathBuf,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
pub(crate) md: Metadata,
|
pub(crate) md: Metadata,
|
||||||
pub(crate) flags: Flags,
|
pub(crate) flags: Flags,
|
||||||
@@ -80,32 +81,6 @@ pub struct NamedFile {
|
|||||||
pub(crate) encoding: Option<ContentEncoding>,
|
pub(crate) encoding: Option<ContentEncoding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NamedFile {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("NamedFile")
|
|
||||||
.field("path", &self.path)
|
|
||||||
.field(
|
|
||||||
"file",
|
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
|
||||||
{
|
|
||||||
&"tokio_uring::File"
|
|
||||||
},
|
|
||||||
#[cfg(not(feature = "experimental-io-uring"))]
|
|
||||||
{
|
|
||||||
&self.file
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.field("modified", &self.modified)
|
|
||||||
.field("md", &self.md)
|
|
||||||
.field("flags", &self.flags)
|
|
||||||
.field("status_code", &self.status_code)
|
|
||||||
.field("content_type", &self.content_type)
|
|
||||||
.field("content_disposition", &self.content_disposition)
|
|
||||||
.field("encoding", &self.encoding)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "experimental-io-uring"))]
|
#[cfg(not(feature = "experimental-io-uring"))]
|
||||||
pub(crate) use std::fs::File;
|
pub(crate) use std::fs::File;
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
#[cfg(feature = "experimental-io-uring")]
|
||||||
@@ -627,7 +602,7 @@ impl Service<ServiceRequest> for NamedFileService {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
actix_service::always_ready!();
|
dev::always_ready!();
|
||||||
|
|
||||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
|
@@ -2,7 +2,7 @@ use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
|
|||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
dev::{Service, ServiceRequest, ServiceResponse},
|
dev::{self, Service, ServiceRequest, ServiceResponse},
|
||||||
error::Error,
|
error::Error,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
http::{header, Method},
|
http::{header, Method},
|
||||||
@@ -98,7 +98,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
actix_service::always_ready!();
|
dev::always_ready!();
|
||||||
|
|
||||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
let is_method_valid = if let Some(guard) = &self.guards {
|
let is_method_valid = if let Some(guard) = &self.guards {
|
||||||
|
@@ -19,12 +19,12 @@ async fn test_utf8_file_contents() {
|
|||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers().get(header::CONTENT_TYPE),
|
res.headers().get(header::CONTENT_TYPE),
|
||||||
Some(&HeaderValue::from_static("text/plain")),
|
Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
|
||||||
);
|
);
|
||||||
|
|
||||||
// prefer UTF-8 encoding
|
// disable UTF-8 attribute
|
||||||
let srv =
|
let srv =
|
||||||
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true)))
|
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
@@ -33,6 +33,6 @@ async fn test_utf8_file_contents() {
|
|||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers().get(header::CONTENT_TYPE),
|
res.headers().get(header::CONTENT_TYPE),
|
||||||
Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
|
Some(&HeaderValue::from_static("text/plain")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
|||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-beta.18"
|
||||||
|
@@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true }
|
|||||||
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2.0.0-rc.2"
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-beta.19"
|
actix-web = "4.0.0-beta.20"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
@@ -42,32 +42,37 @@ mod _new {
|
|||||||
if x < 10 {
|
if x < 10 {
|
||||||
f.write_str("00")?;
|
f.write_str("00")?;
|
||||||
// 0 is handled so it's not possible to have a trailing 0, we can just return
|
// 0 is handled so it's not possible to have a trailing 0, we can just return
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
} else if x < 100 {
|
} else if x < 100 {
|
||||||
f.write_str("0")?;
|
f.write_str("0")?;
|
||||||
if x % 10 == 0 {
|
if x % 10 == 0 {
|
||||||
// trailing 0, divide by 10 and write
|
// trailing 0, divide by 10 and write
|
||||||
itoa::fmt(f, x / 10)
|
itoa_fmt(f, x / 10)
|
||||||
} else {
|
} else {
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// x is in range 101–999
|
// x is in range 101–999
|
||||||
|
|
||||||
if x % 100 == 0 {
|
if x % 100 == 0 {
|
||||||
// two trailing 0s, divide by 100 and write
|
// two trailing 0s, divide by 100 and write
|
||||||
itoa::fmt(f, x / 100)
|
itoa_fmt(f, x / 100)
|
||||||
} else if x % 10 == 0 {
|
} else if x % 10 == 0 {
|
||||||
// one trailing 0, divide by 10 and write
|
// one trailing 0, divide by 10 and write
|
||||||
itoa::fmt(f, x / 10)
|
itoa_fmt(f, x / 10)
|
||||||
} else {
|
} else {
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn itoa_fmt<W: fmt::Write, V: itoa::Integer>(mut wr: W, value: V) -> fmt::Result {
|
||||||
|
let mut buf = itoa::Buffer::new();
|
||||||
|
wr.write_str(buf.format(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod _naive {
|
mod _naive {
|
||||||
|
@@ -15,7 +15,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.19", default-features = false }
|
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
|
@@ -3,6 +3,13 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-rc.1 - 2022-01-14
|
||||||
|
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
||||||
|
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||||
|
|
||||||
|
[#2568]: https://github.com/actix/actix-web/pull/2568
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.4 - 2022-01-04
|
## 0.5.0-beta.4 - 2022-01-04
|
||||||
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-beta.4"
|
version = "0.5.0-rc.1"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||||
use serde::forward_to_deserialize_any;
|
use serde::forward_to_deserialize_any;
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ macro_rules! unsupported_type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! parse_single_value {
|
macro_rules! parse_single_value {
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:expr) => {
|
($trait_fn:ident) => {
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
@@ -34,15 +36,10 @@ macro_rules! parse_single_value {
|
|||||||
.as_str(),
|
.as_str(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let decoded = FULL_QUOTER
|
Value {
|
||||||
.with(|q| q.requote(self.path[0].as_bytes()))
|
value: &self.path[0],
|
||||||
.unwrap_or_else(|| self.path[0].to_owned());
|
}
|
||||||
|
.$trait_fn(visitor)
|
||||||
let v = decoded.parse().map_err(|_| {
|
|
||||||
de::Error::custom(format!("can not parse {:?} to a {}", &self.path[0], $tp))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
visitor.$visit_fn(v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -56,7 +53,8 @@ macro_rules! parse_value {
|
|||||||
{
|
{
|
||||||
let decoded = FULL_QUOTER
|
let decoded = FULL_QUOTER
|
||||||
.with(|q| q.requote(self.value.as_bytes()))
|
.with(|q| q.requote(self.value.as_bytes()))
|
||||||
.unwrap_or_else(|| self.value.to_owned());
|
.map(Cow::Owned)
|
||||||
|
.unwrap_or(Cow::Borrowed(self.value));
|
||||||
|
|
||||||
let v = decoded.parse().map_err(|_| {
|
let v = decoded.parse().map_err(|_| {
|
||||||
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
|
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
|
||||||
@@ -204,26 +202,26 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsupported_type!(deserialize_any, "'any'");
|
unsupported_type!(deserialize_any, "'any'");
|
||||||
unsupported_type!(deserialize_bytes, "bytes");
|
|
||||||
unsupported_type!(deserialize_option, "Option<T>");
|
unsupported_type!(deserialize_option, "Option<T>");
|
||||||
unsupported_type!(deserialize_identifier, "identifier");
|
unsupported_type!(deserialize_identifier, "identifier");
|
||||||
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
||||||
|
|
||||||
parse_single_value!(deserialize_bool, visit_bool, "bool");
|
parse_single_value!(deserialize_bool);
|
||||||
parse_single_value!(deserialize_i8, visit_i8, "i8");
|
parse_single_value!(deserialize_i8);
|
||||||
parse_single_value!(deserialize_i16, visit_i16, "i16");
|
parse_single_value!(deserialize_i16);
|
||||||
parse_single_value!(deserialize_i32, visit_i32, "i32");
|
parse_single_value!(deserialize_i32);
|
||||||
parse_single_value!(deserialize_i64, visit_i64, "i64");
|
parse_single_value!(deserialize_i64);
|
||||||
parse_single_value!(deserialize_u8, visit_u8, "u8");
|
parse_single_value!(deserialize_u8);
|
||||||
parse_single_value!(deserialize_u16, visit_u16, "u16");
|
parse_single_value!(deserialize_u16);
|
||||||
parse_single_value!(deserialize_u32, visit_u32, "u32");
|
parse_single_value!(deserialize_u32);
|
||||||
parse_single_value!(deserialize_u64, visit_u64, "u64");
|
parse_single_value!(deserialize_u64);
|
||||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
parse_single_value!(deserialize_f32);
|
||||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
parse_single_value!(deserialize_f64);
|
||||||
parse_single_value!(deserialize_str, visit_string, "String");
|
parse_single_value!(deserialize_str);
|
||||||
parse_single_value!(deserialize_string, visit_string, "String");
|
parse_single_value!(deserialize_string);
|
||||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
parse_single_value!(deserialize_bytes);
|
||||||
parse_single_value!(deserialize_char, visit_char, "char");
|
parse_single_value!(deserialize_byte_buf);
|
||||||
|
parse_single_value!(deserialize_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParamsDeserializer<'de, T: ResourcePath> {
|
struct ParamsDeserializer<'de, T: ResourcePath> {
|
||||||
@@ -303,8 +301,6 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
|||||||
parse_value!(deserialize_u64, visit_u64, "u64");
|
parse_value!(deserialize_u64, visit_u64, "u64");
|
||||||
parse_value!(deserialize_f32, visit_f32, "f32");
|
parse_value!(deserialize_f32, visit_f32, "f32");
|
||||||
parse_value!(deserialize_f64, visit_f64, "f64");
|
parse_value!(deserialize_f64, visit_f64, "f64");
|
||||||
parse_value!(deserialize_string, visit_string, "String");
|
|
||||||
parse_value!(deserialize_byte_buf, visit_string, "String");
|
|
||||||
parse_value!(deserialize_char, visit_char, "char");
|
parse_value!(deserialize_char, visit_char, "char");
|
||||||
|
|
||||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
@@ -332,18 +328,38 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
|||||||
visitor.visit_unit()
|
visitor.visit_unit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_borrowed_bytes(self.value.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
visitor.visit_borrowed_str(self.value)
|
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) {
|
||||||
|
Some(s) => visitor.visit_string(s),
|
||||||
|
None => visitor.visit_borrowed_str(self.value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) {
|
||||||
|
Some(s) => visitor.visit_byte_buf(s.into()),
|
||||||
|
None => visitor.visit_borrowed_bytes(self.value.as_bytes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
self.deserialize_bytes(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
self.deserialize_str(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
@@ -671,12 +687,12 @@ mod tests {
|
|||||||
fn deserialize_path_decode_seq() {
|
fn deserialize_path_decode_seq() {
|
||||||
let rdef = ResourceDef::new("/{key}/{value}");
|
let rdef = ResourceDef::new("/{key}/{value}");
|
||||||
|
|
||||||
let mut path = Path::new("/%25/%2F");
|
let mut path = Path::new("/%30%25/%30%2F");
|
||||||
rdef.capture_match_info(&mut path);
|
rdef.capture_match_info(&mut path);
|
||||||
let de = PathDeserializer::new(&path);
|
let de = PathDeserializer::new(&path);
|
||||||
let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap();
|
let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap();
|
||||||
assert_eq!(segment.0, "%");
|
assert_eq!(segment.0, "0%");
|
||||||
assert_eq!(segment.1, "/");
|
assert_eq!(segment.1, "0/");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -697,6 +713,32 @@ mod tests {
|
|||||||
assert_eq!(vals.value, "/");
|
assert_eq!(vals.value, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_borrowed() {
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Params<'a> {
|
||||||
|
val: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
let rdef = ResourceDef::new("/{val}");
|
||||||
|
|
||||||
|
let mut path = Path::new("/X");
|
||||||
|
rdef.capture_match_info(&mut path);
|
||||||
|
let de = PathDeserializer::new(&path);
|
||||||
|
let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap();
|
||||||
|
assert_eq!(params.val, "X");
|
||||||
|
let de = PathDeserializer::new(&path);
|
||||||
|
let params: &str = serde::Deserialize::deserialize(de).unwrap();
|
||||||
|
assert_eq!(params, "X");
|
||||||
|
|
||||||
|
let mut path = Path::new("/%2F");
|
||||||
|
rdef.capture_match_info(&mut path);
|
||||||
|
let de = PathDeserializer::new(&path);
|
||||||
|
assert!(<Params<'_> as serde::Deserialize>::deserialize(de).is_err());
|
||||||
|
let de = PathDeserializer::new(&path);
|
||||||
|
assert!(<&str as serde::Deserialize>::deserialize(de).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn test_extract_path_decode() {
|
// fn test_extract_path_decode() {
|
||||||
// let mut router = Router::<()>::default();
|
// let mut router = Router::<()>::default();
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::Index;
|
use std::ops::{DerefMut, Index};
|
||||||
|
|
||||||
use firestorm::profile_method;
|
use firestorm::profile_method;
|
||||||
use serde::de;
|
use serde::de;
|
||||||
@@ -213,8 +213,38 @@ impl<T: ResourcePath> Index<usize> for Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ResourcePath> Resource<T> for Path<T> {
|
impl<T: ResourcePath> Resource for Path<T> {
|
||||||
fn resource_path(&mut self) -> &mut Self {
|
type Path = T;
|
||||||
|
|
||||||
|
fn resource_path(&mut self) -> &mut Path<Self::Path> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, P> Resource for T
|
||||||
|
where
|
||||||
|
T: DerefMut<Target = Path<P>>,
|
||||||
|
P: ResourcePath,
|
||||||
|
{
|
||||||
|
type Path = P;
|
||||||
|
|
||||||
|
fn resource_path(&mut self) -> &mut Path<Self::Path> {
|
||||||
|
&mut *self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deref_impls() {
|
||||||
|
let mut foo = Path::new("/foo");
|
||||||
|
let _ = (&mut foo).resource_path();
|
||||||
|
|
||||||
|
let foo = RefCell::new(foo);
|
||||||
|
let _ = foo.borrow_mut().resource_path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -678,15 +678,14 @@ impl ResourceDef {
|
|||||||
/// assert!(!try_match(&resource, &mut path));
|
/// assert!(!try_match(&resource, &mut path));
|
||||||
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info_fn<R, T, F, U>(
|
pub fn capture_match_info_fn<R, F, U>(
|
||||||
&self,
|
&self,
|
||||||
resource: &mut R,
|
resource: &mut R,
|
||||||
check_fn: F,
|
check_fn: F,
|
||||||
user_data: U,
|
user_data: U,
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
R: Resource<T>,
|
R: Resource,
|
||||||
T: ResourcePath,
|
|
||||||
F: FnOnce(&R, U) -> bool,
|
F: FnOnce(&R, U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(capture_match_info_fn);
|
profile_method!(capture_match_info_fn);
|
||||||
|
@@ -2,8 +2,11 @@ use crate::Path;
|
|||||||
|
|
||||||
// TODO: this trait is necessary, document it
|
// TODO: this trait is necessary, document it
|
||||||
// see impl Resource for ServiceRequest
|
// see impl Resource for ServiceRequest
|
||||||
pub trait Resource<T: ResourcePath> {
|
pub trait Resource {
|
||||||
fn resource_path(&mut self) -> &mut Path<T>;
|
/// Type of resource's path returned in `resource_path`.
|
||||||
|
type Path: ResourcePath;
|
||||||
|
|
||||||
|
fn resource_path(&mut self) -> &mut Path<Self::Path>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ResourcePath {
|
pub trait ResourcePath {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use firestorm::profile_method;
|
use firestorm::profile_method;
|
||||||
|
|
||||||
use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
|
use crate::{IntoPatterns, Resource, ResourceDef};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ResourceId(pub u16);
|
pub struct ResourceId(pub u16);
|
||||||
@@ -26,10 +26,9 @@ impl<T, U> Router<T, U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource<P>,
|
R: Resource,
|
||||||
P: ResourcePath,
|
|
||||||
{
|
{
|
||||||
profile_method!(recognize);
|
profile_method!(recognize);
|
||||||
|
|
||||||
@@ -42,10 +41,9 @@ impl<T, U> Router<T, U> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_mut<R, P>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource<P>,
|
R: Resource,
|
||||||
P: ResourcePath,
|
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut);
|
profile_method!(recognize_mut);
|
||||||
|
|
||||||
@@ -58,11 +56,10 @@ impl<T, U> Router<T, U> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_fn<R, P, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
pub fn recognize_fn<R, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
F: Fn(&R, &Option<U>) -> bool,
|
||||||
R: Resource<P>,
|
R: Resource,
|
||||||
P: ResourcePath,
|
|
||||||
{
|
{
|
||||||
profile_method!(recognize_checked);
|
profile_method!(recognize_checked);
|
||||||
|
|
||||||
@@ -75,15 +72,14 @@ impl<T, U> Router<T, U> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_mut_fn<R, P, F>(
|
pub fn recognize_mut_fn<R, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: &mut R,
|
resource: &mut R,
|
||||||
check: F,
|
check: F,
|
||||||
) -> Option<(&mut T, ResourceId)>
|
) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
F: Fn(&R, &Option<U>) -> bool,
|
||||||
R: Resource<P>,
|
R: Resource,
|
||||||
P: ResourcePath,
|
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut_checked);
|
profile_method!(recognize_mut_checked);
|
||||||
|
|
||||||
|
@@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.11"
|
|||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] }
|
awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] }
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||||
|
@@ -17,7 +17,7 @@ path = "src/lib.rs"
|
|||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = "0.12.0", default-features = false }
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-beta.18"
|
||||||
actix-web = { version = "4.0.0-beta.19", default-features = false }
|
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
|
@@ -25,7 +25,7 @@ actix-macros = "0.2.3"
|
|||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.11"
|
actix-test = "0.1.0-beta.11"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-beta.19"
|
actix-web = "4.0.0-beta.20"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
@@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2"
|
|||||||
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.19", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.20", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
const-str = "0.3"
|
const-str = "0.3"
|
||||||
|
11
scripts/bump
11
scripts/bump
@@ -17,9 +17,18 @@ if [ "$(uname)" = "Darwin" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
CARGO_MANIFEST=$DIR/Cargo.toml
|
CARGO_MANIFEST=$DIR/Cargo.toml
|
||||||
CHANGELOG_FILE=$DIR/CHANGES.md
|
|
||||||
README_FILE=$DIR/README.md
|
README_FILE=$DIR/README.md
|
||||||
|
|
||||||
|
# determine changelog file name
|
||||||
|
if [ -f "$DIR/CHANGES.md" ]; then
|
||||||
|
CHANGELOG_FILE=$DIR/CHANGES.md
|
||||||
|
elif [ -f "$DIR/CHANGELOG.md" ]; then
|
||||||
|
CHANGELOG_FILE=$DIR/CHANGELOG.md
|
||||||
|
else
|
||||||
|
echo "No changelog file found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# get current version
|
# get current version
|
||||||
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
||||||
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")"
|
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")"
|
||||||
|
150
src/app.rs
150
src/app.rs
@@ -51,7 +51,10 @@ impl App<AppEntry> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> App<T> {
|
impl<T> App<T>
|
||||||
|
where
|
||||||
|
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
|
||||||
|
{
|
||||||
/// Set application (root level) data.
|
/// Set application (root level) data.
|
||||||
///
|
///
|
||||||
/// Application data stored with `App::app_data()` method is available through the
|
/// Application data stored with `App::app_data()` method is available through the
|
||||||
@@ -317,65 +320,63 @@ impl<T> App<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers middleware, in the form of a middleware component (type),
|
/// Registers an app-wide middleware.
|
||||||
/// that runs during inbound and/or outbound processing in the request
|
|
||||||
/// life-cycle (request -> response), modifying request/response as
|
|
||||||
/// necessary, across all requests managed by the *Application*.
|
|
||||||
///
|
///
|
||||||
/// Use middleware when you need to read or modify *every* request or
|
/// Registers middleware, in the form of a middleware compo nen t (type), that runs during
|
||||||
/// response in some way.
|
/// inbound and/or outbound processing in the request life-cycle (request -> response),
|
||||||
|
/// modifying request/response as necessary, across all requests managed by the `App`.
|
||||||
///
|
///
|
||||||
/// Notice that the keyword for registering middleware is `wrap`. As you
|
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||||
/// register middleware using `wrap` in the App builder, imagine wrapping
|
|
||||||
/// layers around an inner App. The first middleware layer exposed to a
|
|
||||||
/// Request is the outermost layer-- the *last* registered in
|
|
||||||
/// the builder chain. Consequently, the *first* middleware registered
|
|
||||||
/// in the builder chain is the *last* to execute during request processing.
|
|
||||||
///
|
///
|
||||||
|
/// Middleware can be applied similarly to individual `Scope`s and `Resource`s.
|
||||||
|
/// See [`Scope::wrap`](crate::Scope::wrap) and [`Resource::wrap`].
|
||||||
|
///
|
||||||
|
/// # Middleware Order
|
||||||
|
/// Notice that the keyword for registering middleware is `wrap`. As you register middleware
|
||||||
|
/// using `wrap` in the App builder, imagine wrapping layers around an inner App. The first
|
||||||
|
/// middleware layer exposed to a Request is the outermost layer (i.e., the *last* registered in
|
||||||
|
/// the builder chain). Consequently, the *first* middleware registered in the builder chain is
|
||||||
|
/// the *last* to start executing during request processing.
|
||||||
|
///
|
||||||
|
/// Ordering is less obvious when wrapped services also have middleware applied. In this case,
|
||||||
|
/// middlewares are run in reverse order for `App` _and then_ in reverse order for the
|
||||||
|
/// wrapped service.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_service::Service;
|
|
||||||
/// use actix_web::{middleware, web, App};
|
/// use actix_web::{middleware, web, App};
|
||||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
|
||||||
///
|
///
|
||||||
/// async fn index() -> &'static str {
|
/// async fn index() -> &'static str {
|
||||||
/// "Welcome!"
|
/// "Welcome!"
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .wrap(middleware::Logger::default())
|
||||||
/// .wrap(middleware::Logger::default())
|
/// .route("/index.html", web::get().to(index));
|
||||||
/// .route("/index.html", web::get().to(index));
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn wrap<M, B, B1>(
|
#[doc(alias = "middleware")]
|
||||||
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
|
pub fn wrap<M, B>(
|
||||||
self,
|
self,
|
||||||
mw: M,
|
mw: M,
|
||||||
) -> App<
|
) -> App<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
|
||||||
ServiceRequest,
|
|
||||||
Response = ServiceResponse<B>,
|
|
||||||
Error = Error,
|
|
||||||
Config = (),
|
|
||||||
InitError = (),
|
|
||||||
>,
|
|
||||||
B: MessageBody,
|
|
||||||
M: Transform<
|
M: Transform<
|
||||||
T::Service,
|
T::Service,
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
> + 'static,
|
||||||
B1: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
App {
|
App {
|
||||||
endpoint: apply(mw, self.endpoint),
|
endpoint: apply(mw, self.endpoint),
|
||||||
@@ -388,61 +389,57 @@ impl<T> App<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers middleware, in the form of a closure, that runs during inbound
|
/// Registers an app-wide function middleware.
|
||||||
/// and/or outbound processing in the request life-cycle (request -> response),
|
///
|
||||||
/// modifying request/response as necessary, across all requests managed by
|
/// `mw` is a closure that runs during inbound and/or outbound processing in the request
|
||||||
/// the *Application*.
|
/// life-cycle (request -> response), modifying request/response as necessary, across all
|
||||||
|
/// requests handled by the `App`.
|
||||||
///
|
///
|
||||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||||
///
|
///
|
||||||
|
/// Middleware can also be applied to individual `Scope`s and `Resource`s.
|
||||||
|
///
|
||||||
|
/// See [`App::wrap`] for details on how middlewares compose with each other.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_service::Service;
|
/// use actix_web::{dev::Service as _, middleware, web, App};
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
||||||
///
|
///
|
||||||
/// async fn index() -> &'static str {
|
/// async fn index() -> &'static str {
|
||||||
/// "Welcome!"
|
/// "Welcome!"
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .wrap_fn(|req, srv| {
|
||||||
/// .wrap_fn(|req, srv| {
|
/// let fut = srv.call(req);
|
||||||
/// let fut = srv.call(req);
|
/// async {
|
||||||
/// async {
|
/// let mut res = fut.await?;
|
||||||
/// let mut res = fut.await?;
|
/// res.headers_mut()
|
||||||
/// res.headers_mut().insert(
|
/// .insert(CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
/// Ok(res)
|
||||||
/// );
|
/// }
|
||||||
/// Ok(res)
|
/// })
|
||||||
/// }
|
/// .route("/index.html", web::get().to(index));
|
||||||
/// })
|
|
||||||
/// .route("/index.html", web::get().to(index));
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn wrap_fn<F, R, B, B1>(
|
#[doc(alias = "middleware")]
|
||||||
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
|
pub fn wrap_fn<F, R, B>(
|
||||||
self,
|
self,
|
||||||
mw: F,
|
mw: F,
|
||||||
) -> App<
|
) -> App<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
|
||||||
ServiceRequest,
|
R: Future<Output = Result<ServiceResponse<B>, Error>>,
|
||||||
Response = ServiceResponse<B>,
|
|
||||||
Error = Error,
|
|
||||||
Config = (),
|
|
||||||
InitError = (),
|
|
||||||
>,
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
F: Fn(ServiceRequest, &T::Service) -> R + Clone,
|
|
||||||
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
|
|
||||||
B1: MessageBody,
|
|
||||||
{
|
{
|
||||||
App {
|
App {
|
||||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||||
@@ -458,15 +455,14 @@ impl<T> App<T> {
|
|||||||
|
|
||||||
impl<T, B> IntoServiceFactory<AppInit<T, B>, Request> for App<T>
|
impl<T, B> IntoServiceFactory<AppInit<T, B>, Request> for App<T>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
|
||||||
T: ServiceFactory<
|
T: ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
> + 'static,
|
||||||
T::Future: 'static,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
fn into_factory(self) -> AppInit<T, B> {
|
fn into_factory(self) -> AppInit<T, B> {
|
||||||
AppInit {
|
AppInit {
|
||||||
|
@@ -215,6 +215,17 @@ impl ServiceConfig {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run external configuration as part of the application building process
|
||||||
|
///
|
||||||
|
/// Counterpart to [`App::configure()`](crate::App::configure) that allows for easy nesting.
|
||||||
|
pub fn configure<F>(&mut self, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ServiceConfig),
|
||||||
|
{
|
||||||
|
f(self);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configure route for a specific path.
|
/// Configure route for a specific path.
|
||||||
///
|
///
|
||||||
/// Counterpart to [`App::route()`](crate::App::route).
|
/// Counterpart to [`App::route()`](crate::App::route).
|
||||||
@@ -264,7 +275,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::{Method, StatusCode};
|
use crate::http::{Method, StatusCode};
|
||||||
use crate::test::{call_service, init_service, read_body, TestRequest};
|
use crate::test::{assert_body_eq, call_service, init_service, read_body, TestRequest};
|
||||||
use crate::{web, App, HttpRequest, HttpResponse};
|
use crate::{web, App, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
// allow deprecated `ServiceConfig::data`
|
// allow deprecated `ServiceConfig::data`
|
||||||
@@ -288,38 +299,6 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[actix_rt::test]
|
|
||||||
// async fn test_data_factory() {
|
|
||||||
// let cfg = |cfg: &mut ServiceConfig| {
|
|
||||||
// cfg.data_factory(|| {
|
|
||||||
// sleep(std::time::Duration::from_millis(50)).then(|_| {
|
|
||||||
// println!("READY");
|
|
||||||
// Ok::<_, ()>(10usize)
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let srv =
|
|
||||||
// init_service(App::new().configure(cfg).service(
|
|
||||||
// web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
|
||||||
// ));
|
|
||||||
// let req = TestRequest::default().to_request();
|
|
||||||
// let resp = srv.call(req).await.unwrap();
|
|
||||||
// assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
|
|
||||||
// let cfg2 = |cfg: &mut ServiceConfig| {
|
|
||||||
// cfg.data_factory(|| Ok::<_, ()>(10u32));
|
|
||||||
// };
|
|
||||||
// let srv = init_service(
|
|
||||||
// App::new()
|
|
||||||
// .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()))
|
|
||||||
// .configure(cfg2),
|
|
||||||
// );
|
|
||||||
// let req = TestRequest::default().to_request();
|
|
||||||
// let resp = srv.call(req).await.unwrap();
|
|
||||||
// assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_external_resource() {
|
async fn test_external_resource() {
|
||||||
let srv = init_service(
|
let srv = init_service(
|
||||||
@@ -363,4 +342,22 @@ mod tests {
|
|||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn nested_service_configure() {
|
||||||
|
fn cfg_root(cfg: &mut ServiceConfig) {
|
||||||
|
cfg.configure(cfg_sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cfg_sub(cfg: &mut ServiceConfig) {
|
||||||
|
cfg.route("/", web::get().to(|| async { "hello world" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
let srv = init_service(App::new().configure(cfg_root)).await;
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/").to_request();
|
||||||
|
let res = call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_body_eq!(res, b"hello world");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
future::Future,
|
future::Future,
|
||||||
|
marker::PhantomData,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
@@ -124,12 +125,11 @@ pub trait FromRequest: Sized {
|
|||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
impl<T: 'static> FromRequest for Option<T>
|
impl<T> FromRequest for Option<T>
|
||||||
where
|
where
|
||||||
T: FromRequest,
|
T: FromRequest,
|
||||||
T::Future: 'static,
|
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Infallible;
|
||||||
type Future = FromRequestOptFuture<T::Future>;
|
type Future = FromRequestOptFuture<T::Future>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -152,7 +152,7 @@ where
|
|||||||
Fut: Future<Output = Result<T, E>>,
|
Fut: Future<Output = Result<T, E>>,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
type Output = Result<Option<T>, Error>;
|
type Output = Result<Option<T>, Infallible>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
@@ -211,40 +211,42 @@ where
|
|||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
impl<T> FromRequest for Result<T, T::Error>
|
impl<T, E> FromRequest for Result<T, E>
|
||||||
where
|
where
|
||||||
T: FromRequest + 'static,
|
T: FromRequest,
|
||||||
T::Error: 'static,
|
T::Error: Into<E>,
|
||||||
T::Future: 'static,
|
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Infallible;
|
||||||
type Future = FromRequestResFuture<T::Future>;
|
type Future = FromRequestResFuture<T::Future, E>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
FromRequestResFuture {
|
FromRequestResFuture {
|
||||||
fut: T::from_request(req, payload),
|
fut: T::from_request(req, payload),
|
||||||
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
pub struct FromRequestResFuture<Fut> {
|
pub struct FromRequestResFuture<Fut, E> {
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: Fut,
|
fut: Fut,
|
||||||
|
_phantom: PhantomData<E>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Fut, T, E> Future for FromRequestResFuture<Fut>
|
impl<Fut, T, Ei, E> Future for FromRequestResFuture<Fut, E>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = Result<T, E>>,
|
Fut: Future<Output = Result<T, Ei>>,
|
||||||
|
Ei: Into<E>,
|
||||||
{
|
{
|
||||||
type Output = Result<Result<T, E>, Error>;
|
type Output = Result<Result<T, E>, Infallible>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
let res = ready!(this.fut.poll(cx));
|
let res = ready!(this.fut.poll(cx));
|
||||||
Poll::Ready(Ok(res))
|
Poll::Ready(Ok(res.map_err(Into::into)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,16 +292,6 @@ impl FromRequest for Method {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
impl FromRequest for () {
|
|
||||||
type Error = Infallible;
|
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
|
||||||
|
|
||||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
|
||||||
ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod tuple_from_req {
|
mod tuple_from_req {
|
||||||
@@ -388,6 +380,15 @@ mod tuple_from_req {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRequest for () {
|
||||||
|
type Error = Infallible;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tuple_from_req! { TupleFromRequest1; A }
|
tuple_from_req! { TupleFromRequest1; A }
|
||||||
tuple_from_req! { TupleFromRequest2; A, B }
|
tuple_from_req! { TupleFromRequest2; A, B }
|
||||||
tuple_from_req! { TupleFromRequest3; A, B, C }
|
tuple_from_req! { TupleFromRequest3; A, B, C }
|
||||||
@@ -398,6 +399,8 @@ mod tuple_from_req {
|
|||||||
tuple_from_req! { TupleFromRequest8; A, B, C, D, E, F, G, H }
|
tuple_from_req! { TupleFromRequest8; A, B, C, D, E, F, G, H }
|
||||||
tuple_from_req! { TupleFromRequest9; A, B, C, D, E, F, G, H, I }
|
tuple_from_req! { TupleFromRequest9; A, B, C, D, E, F, G, H, I }
|
||||||
tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J }
|
tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J }
|
||||||
|
tuple_from_req! { TupleFromRequest11; A, B, C, D, E, F, G, H, I, J, K }
|
||||||
|
tuple_from_req! { TupleFromRequest12; A, B, C, D, E, F, G, H, I, J, K, L }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -480,7 +483,14 @@ mod tests {
|
|||||||
.set_payload(Bytes::from_static(b"bye=world"))
|
.set_payload(Bytes::from_static(b"bye=world"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let r = Result::<Form<Info>, Error>::from_request(&req, &mut pl)
|
struct MyError;
|
||||||
|
impl From<Error> for MyError {
|
||||||
|
fn from(_: Error) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Result::<Form<Info>, MyError>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(r.is_err());
|
assert!(r.is_err());
|
||||||
|
22
src/guard.rs
22
src/guard.rs
@@ -54,7 +54,7 @@ use std::{
|
|||||||
|
|
||||||
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
||||||
|
|
||||||
use crate::service::ServiceRequest;
|
use crate::{http::header::Header, service::ServiceRequest};
|
||||||
|
|
||||||
/// Provides access to request parts that are useful during routing.
|
/// Provides access to request parts that are useful during routing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -80,6 +80,26 @@ impl<'a> GuardContext<'a> {
|
|||||||
pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
|
pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
|
||||||
self.req.req_data_mut()
|
self.req.req_data_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts a typed header from the request.
|
||||||
|
///
|
||||||
|
/// Returns `None` if parsing `H` fails.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{guard::fn_guard, http::header};
|
||||||
|
///
|
||||||
|
/// let image_accept_guard = fn_guard(|ctx| {
|
||||||
|
/// match ctx.header::<header::Accept>() {
|
||||||
|
/// Some(hdr) => hdr.preference() == "image/*",
|
||||||
|
/// None => false,
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn header<H: Header>(&self) -> Option<H> {
|
||||||
|
H::parse(self.req).ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interface for routing guards.
|
/// Interface for routing guards.
|
||||||
|
@@ -12,17 +12,14 @@ use crate::{
|
|||||||
/// # What Is A Request Handler
|
/// # What Is A Request Handler
|
||||||
/// A request handler has three requirements:
|
/// A request handler has three requirements:
|
||||||
/// 1. It is an async function (or a function/closure that returns an appropriate future);
|
/// 1. It is an async function (or a function/closure that returns an appropriate future);
|
||||||
/// 1. The function accepts zero or more parameters that implement [`FromRequest`];
|
/// 1. The function parameters (up to 12) implement [`FromRequest`];
|
||||||
/// 1. The async function (or future) resolves to a type that can be converted into an
|
/// 1. The async function (or future) resolves to a type that can be converted into an
|
||||||
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
|
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
|
||||||
///
|
///
|
||||||
/// # Compiler Errors
|
/// # Compiler Errors
|
||||||
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
|
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
|
||||||
/// fulfill one or more of the above requirements.
|
/// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on
|
||||||
///
|
/// implementing [`FromRequest`] and [`Responder`], respectively.
|
||||||
/// Unfortunately we cannot provide a better compile error message (while keeping the trait's
|
|
||||||
/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added
|
|
||||||
/// to Rust.
|
|
||||||
///
|
///
|
||||||
/// # How Do Handlers Receive Variable Numbers Of Arguments
|
/// # How Do Handlers Receive Variable Numbers Of Arguments
|
||||||
/// Rest assured there is no macro magic here; it's just traits.
|
/// Rest assured there is no macro magic here; it's just traits.
|
||||||
@@ -62,13 +59,15 @@ use crate::{
|
|||||||
/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the
|
/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the
|
||||||
/// bounds of the handler call after argument extraction:
|
/// bounds of the handler call after argument extraction:
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// impl<Func, Arg1, Arg2, R> Handler<(Arg1, Arg2), R> for Func
|
/// impl<Func, Arg1, Arg2, Fut> Handler<(Arg1, Arg2)> for Func
|
||||||
/// where
|
/// where
|
||||||
/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static,
|
/// Func: Fn(Arg1, Arg2) -> Fut + Clone + 'static,
|
||||||
/// R: Future,
|
/// Fut: Future,
|
||||||
/// R::Output: Responder,
|
|
||||||
/// {
|
/// {
|
||||||
/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R {
|
/// type Output = Fut::Output;
|
||||||
|
/// type Future = Fut;
|
||||||
|
///
|
||||||
|
/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> Self::Future {
|
||||||
/// (self)(arg1, arg2)
|
/// (self)(arg1, arg2)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@@ -76,7 +75,6 @@ use crate::{
|
|||||||
///
|
///
|
||||||
/// [arity]: https://en.wikipedia.org/wiki/Arity
|
/// [arity]: https://en.wikipedia.org/wiki/Arity
|
||||||
/// [`from_request`]: FromRequest::from_request
|
/// [`from_request`]: FromRequest::from_request
|
||||||
/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628
|
|
||||||
pub trait Handler<Args>: Clone + 'static {
|
pub trait Handler<Args>: Clone + 'static {
|
||||||
type Output;
|
type Output;
|
||||||
type Future: Future<Output = Self::Output>;
|
type Future: Future<Output = Self::Output>;
|
||||||
@@ -121,8 +119,9 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||||
impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func
|
impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func
|
||||||
where Func: Fn($($param),*) -> Fut + Clone + 'static,
|
where
|
||||||
Fut: Future,
|
Func: Fn($($param),*) -> Fut + Clone + 'static,
|
||||||
|
Fut: Future,
|
||||||
{
|
{
|
||||||
type Output = Fut::Output;
|
type Output = Fut::Output;
|
||||||
type Future = Fut;
|
type Future = Fut;
|
||||||
@@ -148,3 +147,25 @@ factory_tuple! { A B C D E F G H I }
|
|||||||
factory_tuple! { A B C D E F G H I J }
|
factory_tuple! { A B C D E F G H I J }
|
||||||
factory_tuple! { A B C D E F G H I J K }
|
factory_tuple! { A B C D E F G H I J K }
|
||||||
factory_tuple! { A B C D E F G H I J K L }
|
factory_tuple! { A B C D E F G H I J K L }
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_impl_handler<T: FromRequest>(_: impl Handler<T>) {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arg_number() {
|
||||||
|
async fn handler_min() {}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(clippy::too_many_arguments, clippy::just_underscores_and_digits)]
|
||||||
|
async fn handler_max(
|
||||||
|
_01: (), _02: (), _03: (), _04: (), _05: (), _06: (),
|
||||||
|
_07: (), _08: (), _09: (), _10: (), _11: (), _12: (),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
assert_impl_handler(handler_min);
|
||||||
|
assert_impl_handler(handler_max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,10 +2,10 @@ use std::cmp::Ordering;
|
|||||||
|
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
use super::QualityItem;
|
use super::{common_header, QualityItem};
|
||||||
use crate::http::header;
|
use crate::http::header;
|
||||||
|
|
||||||
crate::http::header::common_header! {
|
common_header! {
|
||||||
/// `Accept` header, defined
|
/// `Accept` header, defined
|
||||||
/// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)
|
/// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)
|
||||||
///
|
///
|
||||||
|
@@ -150,11 +150,13 @@ mod tests {
|
|||||||
|
|
||||||
use actix_service::IntoService;
|
use actix_service::IntoService;
|
||||||
|
|
||||||
use crate::dev::ServiceRequest;
|
use crate::{
|
||||||
use crate::http::StatusCode;
|
dev::ServiceRequest,
|
||||||
use crate::middleware::{self, Condition, Logger};
|
http::StatusCode,
|
||||||
use crate::test::{call_service, init_service, TestRequest};
|
middleware::{self, Condition, Logger},
|
||||||
use crate::{web, App, HttpResponse};
|
test::{self, call_service, init_service, TestRequest},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
#[cfg(all(feature = "cookies", feature = "__compress"))]
|
#[cfg(all(feature = "cookies", feature = "__compress"))]
|
||||||
@@ -219,4 +221,17 @@ mod tests {
|
|||||||
let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
|
let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn compat_noop_is_noop() {
|
||||||
|
let srv = test::ok_service();
|
||||||
|
|
||||||
|
let mw = Compat::noop()
|
||||||
|
.new_transform(srv.into_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//! Commonly used middleware.
|
//! A collection of common middleware.
|
||||||
|
|
||||||
mod compat;
|
mod compat;
|
||||||
mod condition;
|
mod condition;
|
||||||
|
125
src/resource.rs
125
src/resource.rs
@@ -1,6 +1,6 @@
|
|||||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
use std::{cell::RefCell, fmt, future::Future, rc::Rc};
|
||||||
|
|
||||||
use actix_http::{body::BoxBody, Extensions};
|
use actix_http::Extensions;
|
||||||
use actix_router::{IntoPatterns, Patterns};
|
use actix_router::{IntoPatterns, Patterns};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||||
@@ -42,7 +42,7 @@ use crate::{
|
|||||||
///
|
///
|
||||||
/// If no matching route could be found, *405* response code get returned. Default behavior could be
|
/// If no matching route could be found, *405* response code get returned. Default behavior could be
|
||||||
/// overridden with `default_resource()` method.
|
/// overridden with `default_resource()` method.
|
||||||
pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
|
pub struct Resource<T = ResourceEndpoint> {
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
rdef: Patterns,
|
rdef: Patterns,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
@@ -51,7 +51,6 @@ pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
|
|||||||
guards: Vec<Box<dyn Guard>>,
|
guards: Vec<Box<dyn Guard>>,
|
||||||
default: BoxedHttpServiceFactory,
|
default: BoxedHttpServiceFactory,
|
||||||
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
||||||
_phantom: PhantomData<B>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource {
|
impl Resource {
|
||||||
@@ -69,21 +68,13 @@ impl Resource {
|
|||||||
default: boxed::factory(fn_service(|req: ServiceRequest| async {
|
default: boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||||
Ok(req.into_response(HttpResponse::MethodNotAllowed()))
|
Ok(req.into_response(HttpResponse::MethodNotAllowed()))
|
||||||
})),
|
})),
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> Resource<T, B>
|
impl<T> Resource<T>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
|
||||||
ServiceRequest,
|
|
||||||
Config = (),
|
|
||||||
Response = ServiceResponse<B>,
|
|
||||||
Error = Error,
|
|
||||||
InitError = (),
|
|
||||||
>,
|
|
||||||
B: MessageBody,
|
|
||||||
{
|
{
|
||||||
/// Set resource name.
|
/// Set resource name.
|
||||||
///
|
///
|
||||||
@@ -241,35 +232,35 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a resource middleware.
|
/// Registers a resource middleware.
|
||||||
///
|
///
|
||||||
/// This is similar to `App's` middlewares, but middleware get invoked on resource level.
|
/// `mw` is a middleware component (type), that can modify the request and response across all
|
||||||
/// Resource level middlewares are not allowed to change response
|
/// routes managed by this `Resource`.
|
||||||
/// type (i.e modify response's body).
|
|
||||||
///
|
///
|
||||||
/// **Note**: middlewares get called in opposite order of middlewares registration.
|
/// See [`App::wrap`](crate::App::wrap) for more details.
|
||||||
pub fn wrap<M, B1>(
|
#[doc(alias = "middleware")]
|
||||||
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
|
pub fn wrap<M, B>(
|
||||||
self,
|
self,
|
||||||
mw: M,
|
mw: M,
|
||||||
) -> Resource<
|
) -> Resource<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
B1,
|
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
M: Transform<
|
M: Transform<
|
||||||
T::Service,
|
T::Service,
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
> + 'static,
|
||||||
B1: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
Resource {
|
Resource {
|
||||||
endpoint: apply(mw, self.endpoint),
|
endpoint: apply(mw, self.endpoint),
|
||||||
@@ -280,61 +271,34 @@ where
|
|||||||
default: self.default,
|
default: self.default,
|
||||||
app_data: self.app_data,
|
app_data: self.app_data,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a resource middleware function.
|
/// Registers a resource function middleware.
|
||||||
///
|
///
|
||||||
/// This function accepts instance of `ServiceRequest` type and
|
/// `mw` is a closure that runs during inbound and/or outbound processing in the request
|
||||||
/// mutable reference to the next middleware in chain.
|
/// life-cycle (request -> response), modifying request/response as necessary, across all
|
||||||
|
/// requests handled by the `Resource`.
|
||||||
///
|
///
|
||||||
/// This is similar to `App's` middlewares, but middleware get invoked on resource level.
|
/// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details.
|
||||||
/// Resource level middlewares are not allowed to change response
|
#[doc(alias = "middleware")]
|
||||||
/// type (i.e modify response's body).
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
///
|
pub fn wrap_fn<F, R, B>(
|
||||||
/// ```
|
|
||||||
/// use actix_service::Service;
|
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
|
||||||
///
|
|
||||||
/// async fn index() -> &'static str {
|
|
||||||
/// "Welcome!"
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html")
|
|
||||||
/// .wrap_fn(|req, srv| {
|
|
||||||
/// let fut = srv.call(req);
|
|
||||||
/// async {
|
|
||||||
/// let mut res = fut.await?;
|
|
||||||
/// res.headers_mut().insert(
|
|
||||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
|
||||||
/// );
|
|
||||||
/// Ok(res)
|
|
||||||
/// }
|
|
||||||
/// })
|
|
||||||
/// .route(web::get().to(index)));
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn wrap_fn<F, R, B1>(
|
|
||||||
self,
|
self,
|
||||||
mw: F,
|
mw: F,
|
||||||
) -> Resource<
|
) -> Resource<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
B1,
|
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
F: Fn(ServiceRequest, &T::Service) -> R + Clone,
|
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
|
||||||
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
|
R: Future<Output = Result<ServiceResponse<B>, Error>>,
|
||||||
B1: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
Resource {
|
Resource {
|
||||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||||
@@ -345,7 +309,6 @@ where
|
|||||||
default: self.default,
|
default: self.default,
|
||||||
app_data: self.app_data,
|
app_data: self.app_data,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +336,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> HttpServiceFactory for Resource<T, B>
|
impl<T, B> HttpServiceFactory for Resource<T>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
T: ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
@@ -517,7 +480,7 @@ mod tests {
|
|||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
Method, StatusCode,
|
Method, StatusCode,
|
||||||
},
|
},
|
||||||
middleware::{Compat, DefaultHeaders},
|
middleware::DefaultHeaders,
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
test::{call_service, init_service, TestRequest},
|
test::{call_service, init_service, TestRequest},
|
||||||
web, App, Error, HttpMessage, HttpResponse,
|
web, App, Error, HttpMessage, HttpResponse,
|
||||||
@@ -525,31 +488,35 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_be_returned_from_fn() {
|
fn can_be_returned_from_fn() {
|
||||||
fn my_resource() -> Resource {
|
fn my_resource_1() -> Resource {
|
||||||
web::resource("/test").route(web::get().to(|| async { "hello" }))
|
web::resource("/test1").route(web::get().to(|| async { "hello" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn my_compat_resource() -> Resource<
|
fn my_resource_2() -> Resource<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse,
|
Response = ServiceResponse<impl MessageBody>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
> {
|
> {
|
||||||
web::resource("/test-compat")
|
web::resource("/test2")
|
||||||
.wrap_fn(|req, srv| {
|
.wrap_fn(|req, srv| {
|
||||||
let fut = srv.call(req);
|
let fut = srv.call(req);
|
||||||
async { Ok(fut.await?.map_into_right_body::<()>()) }
|
async { Ok(fut.await?.map_into_right_body::<()>()) }
|
||||||
})
|
})
|
||||||
.wrap(Compat::noop())
|
|
||||||
.route(web::get().to(|| async { "hello" }))
|
.route(web::get().to(|| async { "hello" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn my_resource_3() -> impl HttpServiceFactory {
|
||||||
|
web::resource("/test3").route(web::get().to(|| async { "hello" }))
|
||||||
|
}
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.service(my_resource())
|
.service(my_resource_1())
|
||||||
.service(my_compat_resource());
|
.service(my_resource_2())
|
||||||
|
.service(my_resource_3());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@@ -23,7 +23,7 @@ use cookie::{Cookie, CookieJar};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, JsonPayloadError},
|
error::{Error, JsonPayloadError},
|
||||||
BoxError, HttpResponse,
|
BoxError, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP response builder.
|
/// An HTTP response builder.
|
||||||
@@ -424,6 +424,15 @@ impl Future for HttpResponseBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for HttpResponseBuilder {
|
||||||
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
self.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::body;
|
use actix_http::body;
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{EitherBody, MessageBody},
|
body::EitherBody, error::HttpError, header::HeaderMap, header::TryIntoHeaderPair,
|
||||||
error::HttpError,
|
|
||||||
header::HeaderMap,
|
|
||||||
header::TryIntoHeaderPair,
|
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{BoxError, HttpRequest, HttpResponse, Responder};
|
use crate::{HttpRequest, HttpResponse, Responder};
|
||||||
|
|
||||||
/// Allows overriding status code and headers for a [`Responder`].
|
/// Allows overriding status code and headers for a [`Responder`].
|
||||||
///
|
///
|
||||||
@@ -143,7 +140,6 @@ impl<R: Responder> CustomizeResponder<R> {
|
|||||||
impl<T> Responder for CustomizeResponder<T>
|
impl<T> Responder for CustomizeResponder<T>
|
||||||
where
|
where
|
||||||
T: Responder,
|
T: Responder,
|
||||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
|
||||||
{
|
{
|
||||||
type Body = EitherBody<T::Body>;
|
type Body = EitherBody<T::Body>;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ use actix_http::{
|
|||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
use crate::{Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
use super::CustomizeResponder;
|
use super::CustomizeResponder;
|
||||||
|
|
||||||
@@ -57,15 +57,6 @@ pub trait Responder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for HttpResponse {
|
|
||||||
type Body = BoxBody;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for actix_http::Response<BoxBody> {
|
impl Responder for actix_http::Response<BoxBody> {
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
@@ -75,15 +66,6 @@ impl Responder for actix_http::Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for HttpResponseBuilder {
|
|
||||||
type Body = BoxBody;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
|
||||||
self.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for actix_http::ResponseBuilder {
|
impl Responder for actix_http::ResponseBuilder {
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
@@ -96,7 +78,6 @@ impl Responder for actix_http::ResponseBuilder {
|
|||||||
impl<T> Responder for Option<T>
|
impl<T> Responder for Option<T>
|
||||||
where
|
where
|
||||||
T: Responder,
|
T: Responder,
|
||||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
|
||||||
{
|
{
|
||||||
type Body = EitherBody<T::Body>;
|
type Body = EitherBody<T::Body>;
|
||||||
|
|
||||||
@@ -111,7 +92,6 @@ where
|
|||||||
impl<T, E> Responder for Result<T, E>
|
impl<T, E> Responder for Result<T, E>
|
||||||
where
|
where
|
||||||
T: Responder,
|
T: Responder,
|
||||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
type Body = EitherBody<T::Body>;
|
type Body = EitherBody<T::Body>;
|
||||||
|
@@ -22,7 +22,7 @@ use {
|
|||||||
cookie::Cookie,
|
cookie::Cookie,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::Error, HttpResponseBuilder};
|
use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder};
|
||||||
|
|
||||||
/// An outgoing response.
|
/// An outgoing response.
|
||||||
pub struct HttpResponse<B = BoxBody> {
|
pub struct HttpResponse<B = BoxBody> {
|
||||||
@@ -311,6 +311,18 @@ impl Future for HttpResponse<BoxBody> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<B> Responder for HttpResponse<B>
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Body = B;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
pub struct CookieIter<'a> {
|
pub struct CookieIter<'a> {
|
||||||
iter: std::slice::Iter<'a, HeaderValue>,
|
iter: std::slice::Iter<'a, HeaderValue>,
|
||||||
@@ -333,9 +345,16 @@ impl<'a> Iterator for CookieIter<'a> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use static_assertions::assert_impl_all;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::header::{HeaderValue, COOKIE};
|
use crate::http::header::{HeaderValue, COOKIE};
|
||||||
|
|
||||||
|
assert_impl_all!(HttpResponse: Responder);
|
||||||
|
assert_impl_all!(HttpResponse<String>: Responder);
|
||||||
|
assert_impl_all!(HttpResponse<&'static str>: Responder);
|
||||||
|
assert_impl_all!(HttpResponse<crate::body::None>: Responder);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
let resp = HttpResponse::Ok()
|
let resp = HttpResponse::Ok()
|
||||||
|
116
src/scope.rs
116
src/scope.rs
@@ -1,9 +1,6 @@
|
|||||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc};
|
use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{body::MessageBody, Extensions};
|
||||||
body::{BoxBody, MessageBody},
|
|
||||||
Extensions,
|
|
||||||
};
|
|
||||||
use actix_router::{ResourceDef, Router};
|
use actix_router::{ResourceDef, Router};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
|
apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
|
||||||
@@ -57,7 +54,7 @@ type Guards = Vec<Box<dyn Guard>>;
|
|||||||
///
|
///
|
||||||
/// [pat]: crate::dev::ResourceDef#prefix-resources
|
/// [pat]: crate::dev::ResourceDef#prefix-resources
|
||||||
/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
|
/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
|
||||||
pub struct Scope<T = ScopeEndpoint, B = BoxBody> {
|
pub struct Scope<T = ScopeEndpoint> {
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
rdef: String,
|
rdef: String,
|
||||||
app_data: Option<Extensions>,
|
app_data: Option<Extensions>,
|
||||||
@@ -66,7 +63,6 @@ pub struct Scope<T = ScopeEndpoint, B = BoxBody> {
|
|||||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
||||||
_phantom: PhantomData<B>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
@@ -83,21 +79,13 @@ impl Scope {
|
|||||||
default: None,
|
default: None,
|
||||||
external: Vec::new(),
|
external: Vec::new(),
|
||||||
factory_ref,
|
factory_ref,
|
||||||
_phantom: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> Scope<T, B>
|
impl<T> Scope<T>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
|
||||||
ServiceRequest,
|
|
||||||
Config = (),
|
|
||||||
Response = ServiceResponse<B>,
|
|
||||||
Error = Error,
|
|
||||||
InitError = (),
|
|
||||||
>,
|
|
||||||
B: 'static,
|
|
||||||
{
|
{
|
||||||
/// Add match guard to a scope.
|
/// Add match guard to a scope.
|
||||||
///
|
///
|
||||||
@@ -296,32 +284,35 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers middleware, in the form of a middleware component (type), that runs during inbound
|
/// Registers a scope-wide middleware.
|
||||||
/// processing in the request life-cycle (request -> response), modifying request as necessary,
|
|
||||||
/// across all requests managed by the *Scope*.
|
|
||||||
///
|
///
|
||||||
/// Use middleware when you need to read or modify *every* request in some way.
|
/// `mw` is a middleware component (type), that can modify the request and response across all
|
||||||
pub fn wrap<M, B1>(
|
/// sub-resources managed by this `Scope`.
|
||||||
|
///
|
||||||
|
/// See [`App::wrap`](crate::App::wrap) for more details.
|
||||||
|
#[doc(alias = "middleware")]
|
||||||
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
|
pub fn wrap<M, B>(
|
||||||
self,
|
self,
|
||||||
mw: M,
|
mw: M,
|
||||||
) -> Scope<
|
) -> Scope<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
B1,
|
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
M: Transform<
|
M: Transform<
|
||||||
T::Service,
|
T::Service,
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
> + 'static,
|
||||||
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
Scope {
|
Scope {
|
||||||
endpoint: apply(mw, self.endpoint),
|
endpoint: apply(mw, self.endpoint),
|
||||||
@@ -332,54 +323,34 @@ where
|
|||||||
default: self.default,
|
default: self.default,
|
||||||
external: self.external,
|
external: self.external,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers middleware, in the form of a closure, that runs during inbound processing in the
|
/// Registers a scope-wide function middleware.
|
||||||
/// request life-cycle (request -> response), modifying request as necessary, across all
|
|
||||||
/// requests managed by the *Scope*.
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// `mw` is a closure that runs during inbound and/or outbound processing in the request
|
||||||
/// ```
|
/// life-cycle (request -> response), modifying request/response as necessary, across all
|
||||||
/// use actix_service::Service;
|
/// requests handled by the `Scope`.
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
|
||||||
///
|
///
|
||||||
/// async fn index() -> &'static str {
|
/// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details.
|
||||||
/// "Welcome!"
|
#[doc(alias = "middleware")]
|
||||||
/// }
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
///
|
pub fn wrap_fn<F, R, B>(
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::scope("/app")
|
|
||||||
/// .wrap_fn(|req, srv| {
|
|
||||||
/// let fut = srv.call(req);
|
|
||||||
/// async {
|
|
||||||
/// let mut res = fut.await?;
|
|
||||||
/// res.headers_mut().insert(
|
|
||||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
|
||||||
/// );
|
|
||||||
/// Ok(res)
|
|
||||||
/// }
|
|
||||||
/// })
|
|
||||||
/// .route("/index.html", web::get().to(index)));
|
|
||||||
/// ```
|
|
||||||
pub fn wrap_fn<F, R, B1>(
|
|
||||||
self,
|
self,
|
||||||
mw: F,
|
mw: F,
|
||||||
) -> Scope<
|
) -> Scope<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse<B1>,
|
Response = ServiceResponse<B>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
B1,
|
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
F: Fn(ServiceRequest, &T::Service) -> R + Clone,
|
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
|
||||||
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
|
R: Future<Output = Result<ServiceResponse<B>, Error>>,
|
||||||
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
Scope {
|
Scope {
|
||||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||||
@@ -390,12 +361,11 @@ where
|
|||||||
default: self.default,
|
default: self.default,
|
||||||
external: self.external,
|
external: self.external,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> HttpServiceFactory for Scope<T, B>
|
impl<T, B> HttpServiceFactory for Scope<T>
|
||||||
where
|
where
|
||||||
T: ServiceFactory<
|
T: ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
@@ -596,7 +566,7 @@ mod tests {
|
|||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
Method, StatusCode,
|
Method, StatusCode,
|
||||||
},
|
},
|
||||||
middleware::{Compat, DefaultHeaders},
|
middleware::DefaultHeaders,
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
|
test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
|
||||||
web, App, HttpMessage, HttpRequest, HttpResponse,
|
web, App, HttpMessage, HttpRequest, HttpResponse,
|
||||||
@@ -604,16 +574,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_be_returned_from_fn() {
|
fn can_be_returned_from_fn() {
|
||||||
fn my_scope() -> Scope {
|
fn my_scope_1() -> Scope {
|
||||||
web::scope("/test")
|
web::scope("/test")
|
||||||
.service(web::resource("").route(web::get().to(|| async { "hello" })))
|
.service(web::resource("").route(web::get().to(|| async { "hello" })))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn my_compat_scope() -> Scope<
|
fn my_scope_2() -> Scope<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Response = ServiceResponse,
|
Response = ServiceResponse<impl MessageBody>,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
@@ -623,11 +593,17 @@ mod tests {
|
|||||||
let fut = srv.call(req);
|
let fut = srv.call(req);
|
||||||
async { Ok(fut.await?.map_into_right_body::<()>()) }
|
async { Ok(fut.await?.map_into_right_body::<()>()) }
|
||||||
})
|
})
|
||||||
.wrap(Compat::noop())
|
|
||||||
.service(web::resource("").route(web::get().to(|| async { "hello" })))
|
.service(web::resource("").route(web::get().to(|| async { "hello" })))
|
||||||
}
|
}
|
||||||
|
|
||||||
App::new().service(my_scope()).service(my_compat_scope());
|
fn my_scope_3() -> impl HttpServiceFactory {
|
||||||
|
my_scope_2()
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.service(my_scope_1())
|
||||||
|
.service(my_scope_2())
|
||||||
|
.service(my_scope_3());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@@ -307,9 +307,11 @@ impl ServiceRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource<Url> for ServiceRequest {
|
impl Resource for ServiceRequest {
|
||||||
|
type Path = Url;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn resource_path(&mut self) -> &mut Path<Url> {
|
fn resource_path(&mut self) -> &mut Path<Self::Path> {
|
||||||
self.match_info_mut()
|
self.match_info_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::fmt;
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
use actix_http::Request;
|
use actix_http::Request;
|
||||||
use actix_service::IntoServiceFactory;
|
use actix_service::IntoServiceFactory;
|
||||||
@@ -135,7 +135,6 @@ pub async fn call_and_read_body<S, B>(app: &S, req: Request) -> Bytes
|
|||||||
where
|
where
|
||||||
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
{
|
{
|
||||||
let res = call_service(app, req).await;
|
let res = call_service(app, req).await;
|
||||||
read_body(res).await
|
read_body(res).await
|
||||||
@@ -147,7 +146,6 @@ pub async fn read_response<S, B>(app: &S, req: Request) -> Bytes
|
|||||||
where
|
where
|
||||||
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
{
|
{
|
||||||
let res = call_service(app, req).await;
|
let res = call_service(app, req).await;
|
||||||
read_body(res).await
|
read_body(res).await
|
||||||
@@ -186,11 +184,11 @@ where
|
|||||||
pub async fn read_body<B>(res: ServiceResponse<B>) -> Bytes
|
pub async fn read_body<B>(res: ServiceResponse<B>) -> Bytes
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
{
|
{
|
||||||
let body = res.into_body();
|
let body = res.into_body();
|
||||||
body::to_bytes(body)
|
body::to_bytes(body)
|
||||||
.await
|
.await
|
||||||
|
.map_err(Into::<Box<dyn StdError>>::into)
|
||||||
.expect("error reading test response body")
|
.expect("error reading test response body")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +238,6 @@ where
|
|||||||
pub async fn read_body_json<T, B>(res: ServiceResponse<B>) -> T
|
pub async fn read_body_json<T, B>(res: ServiceResponse<B>) -> T
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
let body = read_body(res).await;
|
let body = read_body(res).await;
|
||||||
@@ -300,7 +297,6 @@ pub async fn call_and_read_body_json<S, B, T>(app: &S, req: Request) -> T
|
|||||||
where
|
where
|
||||||
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
let res = call_service(app, req).await;
|
let res = call_service(app, req).await;
|
||||||
@@ -313,7 +309,6 @@ pub async fn read_response_json<S, B, T>(app: &S, req: Request) -> T
|
|||||||
where
|
where
|
||||||
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: fmt::Debug,
|
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
call_and_read_body_json(app, req).await
|
call_and_read_body_json(app, req).await
|
||||||
@@ -325,7 +320,10 @@ mod tests {
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{http::header, test::TestRequest, web, App, HttpMessage, HttpResponse};
|
use crate::{
|
||||||
|
dev::ServiceRequest, http::header, test::TestRequest, web, App, HttpMessage,
|
||||||
|
HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_request_methods() {
|
async fn test_request_methods() {
|
||||||
@@ -471,4 +469,37 @@ mod tests {
|
|||||||
assert_eq!(&result.id, "12345");
|
assert_eq!(&result.id, "12345");
|
||||||
assert_eq!(&result.name, "User name");
|
assert_eq!(&result.name, "User name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn return_opaque_types() {
|
||||||
|
fn test_app() -> App<
|
||||||
|
impl ServiceFactory<
|
||||||
|
ServiceRequest,
|
||||||
|
Config = (),
|
||||||
|
Response = ServiceResponse<impl MessageBody>,
|
||||||
|
Error = crate::Error,
|
||||||
|
InitError = (),
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
App::new().service(web::resource("/people").route(
|
||||||
|
web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_service(
|
||||||
|
) -> impl Service<Request, Response = ServiceResponse<impl MessageBody>, Error = crate::Error>
|
||||||
|
{
|
||||||
|
init_service(test_app()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compile_test(mut req: Vec<Request>) {
|
||||||
|
let svc = test_service().await;
|
||||||
|
call_service(&svc, req.pop().unwrap()).await;
|
||||||
|
call_and_read_body(&svc, req.pop().unwrap()).await;
|
||||||
|
read_body(call_service(&svc, req.pop().unwrap()).await).await;
|
||||||
|
let _: String = call_and_read_body_json(&svc, req.pop().unwrap()).await;
|
||||||
|
let _: String = read_body_json(call_service(&svc, req.pop().unwrap()).await).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user