mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into data-doc
This commit is contained in:
commit
0e87f1eb3b
25
CHANGES.md
25
CHANGES.md
|
@ -2,6 +2,27 @@
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
### Added
|
### Added
|
||||||
|
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
|
||||||
|
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
|
||||||
|
Making it more simple and performant. [#1891]
|
||||||
|
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail.
|
||||||
|
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
|
||||||
|
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Public field of `web::Path` has been made private. [#1894]
|
||||||
|
* Public field of `web::Query` has been made private. [#1894]
|
||||||
|
|
||||||
|
[#1891]: https://github.com/actix/actix-web/pull/1891
|
||||||
|
[#1893]: https://github.com/actix/actix-web/pull/1893
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
|
### Added
|
||||||
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
||||||
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
|
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
|
||||||
|
|
||||||
|
@ -21,13 +42,15 @@
|
||||||
### Removed
|
### Removed
|
||||||
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
||||||
exposed directly by the `middleware` module.
|
exposed directly by the `middleware` module.
|
||||||
|
* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported
|
||||||
|
from `actix_web::error` module. [#1878]
|
||||||
|
|
||||||
[#1812]: https://github.com/actix/actix-web/pull/1812
|
[#1812]: https://github.com/actix/actix-web/pull/1812
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1852]: https://github.com/actix/actix-web/pull/1852
|
[#1852]: https://github.com/actix/actix-web/pull/1852
|
||||||
[#1865]: https://github.com/actix/actix-web/pull/1865
|
[#1865]: https://github.com/actix/actix-web/pull/1865
|
||||||
[#1875]: https://github.com/actix/actix-web/pull/1875
|
[#1875]: https://github.com/actix/actix-web/pull/1875
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
## 3.3.2 - 2020-12-01
|
## 3.3.2 - 2020-12-01
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "3.3.2"
|
version = "4.0.0-beta.1"
|
||||||
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"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -76,20 +76,20 @@ required-features = ["rustls"]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-macros = "0.1.0"
|
actix-macros = "0.1.0"
|
||||||
actix-router = "0.2.4"
|
actix-router = "0.2.4"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.3"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-threadpool = "0.3.1"
|
|
||||||
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-web-codegen = "0.4.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "2.2.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
awc = { version = "2.0.3", default-features = false }
|
awc = { version = "3.0.0-beta.1", default-features = false }
|
||||||
|
|
||||||
ahash = "0.6"
|
ahash = "0.6"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@ -109,7 +109,7 @@ smallvec = "1.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = "0.11.0-beta.1"
|
||||||
actix-http = { version = "2.2.0", features = ["actors"] }
|
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -126,6 +126,7 @@ codegen-units = 1
|
||||||
actix-web = { path = "." }
|
actix-web = { path = "." }
|
||||||
actix-http = { path = "actix-http" }
|
actix-http = { path = "actix-http" }
|
||||||
actix-http-test = { path = "actix-http-test" }
|
actix-http-test = { path = "actix-http-test" }
|
||||||
|
actix-web-actors = { path = "actix-web-actors" }
|
||||||
actix-web-codegen = { path = "actix-web-codegen" }
|
actix-web-codegen = { path = "actix-web-codegen" }
|
||||||
actix-multipart = { path = "actix-multipart" }
|
actix-multipart = { path = "actix-multipart" }
|
||||||
actix-files = { path = "actix-files" }
|
actix-files = { path = "actix-files" }
|
||||||
|
@ -138,3 +139,7 @@ harness = false
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "service"
|
name = "service"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "responder"
|
||||||
|
harness = false
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||||
|
|
||||||
|
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||||
|
|
||||||
|
## 0.6.0-beta.1 - 2021-01-07
|
||||||
* `HttpRange::parse` now has its own error type.
|
* `HttpRange::parse` now has its own error type.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.5.0"
|
version = "0.6.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static file serving for Actix Web"
|
description = "Static file serving for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -17,8 +17,8 @@ name = "actix_files"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "3.0.0", default-features = false }
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.3"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
|
@ -31,5 +31,5 @@ percent-encoding = "2.1"
|
||||||
v_htmlescape = "0.12"
|
v_htmlescape = "0.12"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-web = "3.0.0"
|
actix-web = "4.0.0-beta.1"
|
||||||
|
|
|
@ -8,17 +8,11 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
error::{BlockingError, Error},
|
error::{Error, ErrorInternalServerError},
|
||||||
web,
|
rt::task::{spawn_blocking, JoinHandle},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use futures_util::future::{FutureExt, LocalBoxFuture};
|
|
||||||
|
|
||||||
use crate::handle_error;
|
|
||||||
|
|
||||||
type ChunkedBoxFuture =
|
|
||||||
LocalBoxFuture<'static, Result<(File, Bytes), BlockingError<io::Error>>>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// A helper created from a `std::fs::File` which reads the file
|
/// A helper created from a `std::fs::File` which reads the file
|
||||||
|
@ -27,7 +21,7 @@ pub struct ChunkedReadFile {
|
||||||
pub(crate) size: u64,
|
pub(crate) size: u64,
|
||||||
pub(crate) offset: u64,
|
pub(crate) offset: u64,
|
||||||
pub(crate) file: Option<File>,
|
pub(crate) file: Option<File>,
|
||||||
pub(crate) fut: Option<ChunkedBoxFuture>,
|
pub(crate) fut: Option<JoinHandle<Result<(File, Bytes), io::Error>>>,
|
||||||
pub(crate) counter: u64,
|
pub(crate) counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,18 +39,20 @@ impl Stream for ChunkedReadFile {
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
return match ready!(Pin::new(fut).poll(cx)) {
|
let res = match ready!(Pin::new(fut).poll(cx)) {
|
||||||
Ok((file, bytes)) => {
|
Ok(Ok((file, bytes))) => {
|
||||||
self.fut.take();
|
self.fut.take();
|
||||||
self.file = Some(file);
|
self.file = Some(file);
|
||||||
|
|
||||||
self.offset += bytes.len() as u64;
|
self.offset += bytes.len() as u64;
|
||||||
self.counter += bytes.len() as u64;
|
self.counter += bytes.len() as u64;
|
||||||
|
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
Err(e) => Poll::Ready(Some(Err(handle_error(e)))),
|
Ok(Err(e)) => Err(e.into()),
|
||||||
|
Err(_) => Err(ErrorInternalServerError("Unexpected error")),
|
||||||
};
|
};
|
||||||
|
return Poll::Ready(Some(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = self.size;
|
let size = self.size;
|
||||||
|
@ -68,10 +64,8 @@ impl Stream for ChunkedReadFile {
|
||||||
} else {
|
} else {
|
||||||
let mut file = self.file.take().expect("Use after completion");
|
let mut file = self.file.take().expect("Use after completion");
|
||||||
|
|
||||||
self.fut = Some(
|
self.fut = Some(spawn_blocking(move || {
|
||||||
web::block(move || {
|
let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
||||||
let max_bytes =
|
|
||||||
cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(max_bytes);
|
let mut buf = Vec::with_capacity(max_bytes);
|
||||||
file.seek(io::SeekFrom::Start(offset))?;
|
file.seek(io::SeekFrom::Start(offset))?;
|
||||||
|
@ -84,9 +78,7 @@ impl Stream for ChunkedReadFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((file, Bytes::from(buf)))
|
Ok((file, Bytes::from(buf)))
|
||||||
})
|
}));
|
||||||
.boxed_local(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.poll_next(cx)
|
self.poll_next(cx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,9 @@ impl Files {
|
||||||
/// be inaccessible. Register more specific handlers and services first.
|
/// be inaccessible. Register more specific handlers and services first.
|
||||||
///
|
///
|
||||||
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
|
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
|
||||||
/// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed
|
/// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are
|
||||||
/// by setting ACTIX_THREADPOOL environment variable.
|
/// adjusted with work load. More threads would spawn when need and threads goes idle for a
|
||||||
|
/// period of time would be de-spawned.
|
||||||
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
||||||
let orig_dir = serve_from.into();
|
let orig_dir = serve_from.into();
|
||||||
let dir = match orig_dir.canonicalize() {
|
let dir = match orig_dir.canonicalize() {
|
||||||
|
|
|
@ -14,12 +14,10 @@
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![warn(missing_docs, missing_debug_implementations)]
|
#![warn(missing_docs, missing_debug_implementations)]
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{ServiceRequest, ServiceResponse},
|
dev::{ServiceRequest, ServiceResponse},
|
||||||
error::{BlockingError, Error, ErrorInternalServerError},
|
error::Error,
|
||||||
http::header::DispositionType,
|
http::header::DispositionType,
|
||||||
};
|
};
|
||||||
use mime_guess::from_ext;
|
use mime_guess::from_ext;
|
||||||
|
@ -56,13 +54,6 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
|
||||||
from_ext(ext).first_or_octet_stream()
|
from_ext(ext).first_or_octet_stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_error(err: BlockingError<io::Error>) -> Error {
|
|
||||||
match err {
|
|
||||||
BlockingError::Error(err) => err.into(),
|
|
||||||
BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
|
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -116,6 +107,18 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_modified_since_without_if_none_match_same() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(header::IF_MODIFIED_SINCE, since)
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_if_modified_since_with_if_none_match() {
|
async fn test_if_modified_since_with_if_none_match() {
|
||||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
@ -130,6 +133,30 @@ mod tests {
|
||||||
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_unmodified_since() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(header::IF_UNMODIFIED_SINCE, since)
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_unmodified_since_failed() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(header::IF_UNMODIFIED_SINCE, since)
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_text() {
|
async fn test_named_file_text() {
|
||||||
assert!(NamedFile::open("test--").is_err());
|
assert!(NamedFile::open("test--").is_err());
|
||||||
|
|
|
@ -16,10 +16,9 @@ use actix_web::{
|
||||||
},
|
},
|
||||||
ContentEncoding, StatusCode,
|
ContentEncoding, StatusCode,
|
||||||
},
|
},
|
||||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use futures_util::future::{ready, Ready};
|
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
use crate::ChunkedReadFile;
|
use crate::ChunkedReadFile;
|
||||||
|
@ -277,7 +276,7 @@ impl NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an `HttpResponse` with file as a streaming body.
|
/// Creates an `HttpResponse` with file as a streaming body.
|
||||||
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
|
||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut res = HttpResponse::build(self.status_code);
|
let mut res = HttpResponse::build(self.status_code);
|
||||||
|
|
||||||
|
@ -307,7 +306,7 @@ impl NamedFile {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(res.streaming(reader));
|
return res.streaming(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let etag = if self.flags.contains(Flags::ETAG) {
|
let etag = if self.flags.contains(Flags::ETAG) {
|
||||||
|
@ -332,7 +331,7 @@ impl NamedFile {
|
||||||
let t2: SystemTime = since.clone().into();
|
let t2: SystemTime = since.clone().into();
|
||||||
|
|
||||||
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
||||||
(Ok(t1), Ok(t2)) => t1 > t2,
|
(Ok(t1), Ok(t2)) => t1.as_secs() > t2.as_secs(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -351,7 +350,7 @@ impl NamedFile {
|
||||||
let t2: SystemTime = since.clone().into();
|
let t2: SystemTime = since.clone().into();
|
||||||
|
|
||||||
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
||||||
(Ok(t1), Ok(t2)) => t1 <= t2,
|
(Ok(t1), Ok(t2)) => t1.as_secs() <= t2.as_secs(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -411,17 +410,17 @@ impl NamedFile {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
||||||
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
|
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return Ok(resp.status(StatusCode::BAD_REQUEST).finish());
|
return resp.status(StatusCode::BAD_REQUEST).finish();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if precondition_failed {
|
if precondition_failed {
|
||||||
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
|
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||||
} else if not_modified {
|
} else if not_modified {
|
||||||
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish());
|
return resp.status(StatusCode::NOT_MODIFIED).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = ChunkedReadFile {
|
let reader = ChunkedReadFile {
|
||||||
|
@ -436,7 +435,7 @@ impl NamedFile {
|
||||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
resp.status(StatusCode::PARTIAL_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(resp.body(SizedStream::new(length, reader)))
|
resp.body(SizedStream::new(length, reader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,10 +494,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
type Error = Error;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<HttpResponse, Error>>;
|
self.into_response(req)
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
ready(self.into_response(req))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,10 +120,8 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
|
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
Either::Left(ok(match named_file.into_response(&req) {
|
let res = named_file.into_response(&req);
|
||||||
Ok(item) => ServiceResponse::new(req, item),
|
Either::Left(ok(ServiceResponse::new(req, res)))
|
||||||
Err(e) => ServiceResponse::from_err(e, req),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
Err(e) => self.handle_err(e, req),
|
Err(e) => self.handle_err(e, req),
|
||||||
}
|
}
|
||||||
|
@ -154,12 +152,8 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
|
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
match named_file.into_response(&req) {
|
let res = named_file.into_response(&req);
|
||||||
Ok(item) => {
|
Either::Left(ok(ServiceResponse::new(req, res)))
|
||||||
Either::Left(ok(ServiceResponse::new(req.clone(), item)))
|
|
||||||
}
|
|
||||||
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => self.handle_err(e, req),
|
Err(e) => self.handle_err(e, req),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "2.1.0"
|
version = "3.0.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Various helpers for Actix applications to use during testing"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -29,13 +29,13 @@ default = []
|
||||||
openssl = ["open-ssl", "awc/openssl"]
|
openssl = ["open-ssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.3"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.2"
|
||||||
awc = "2.0.0"
|
awc = "3.0.0-beta.1"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -51,5 +51,5 @@ time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||||
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = "3.0.0"
|
actix-web = "4.0.0-beta.1"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
|
||||||
|
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
### Added
|
||||||
|
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
||||||
* Bumped `rand` to `0.8`.
|
* Bumped `rand` to `0.8`.
|
||||||
|
@ -16,11 +25,14 @@
|
||||||
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
|
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
|
||||||
due to the removal of this type from `tokio-openssl` crate. openssl handshake
|
due to the removal of this type from `tokio-openssl` crate. openssl handshake
|
||||||
error would return as `ConnectError::SslError`. [#1813]
|
error would return as `ConnectError::SslError`. [#1813]
|
||||||
|
* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
|
||||||
|
Due to this change `actix_threadpool::BlockingError` type is moved into
|
||||||
|
`actix_http::error` module. [#1878]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1857]: https://github.com/actix/actix-web/pull/1857
|
[#1857]: https://github.com/actix/actix-web/pull/1857
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
## 2.2.0 - 2020-11-25
|
## 2.2.0 - 2020-11-25
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "2.2.0"
|
version = "3.0.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -40,11 +40,10 @@ secure-cookies = ["cookie/secure"]
|
||||||
actors = ["actix"]
|
actors = ["actix"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.3"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-threadpool = "0.3.1"
|
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix = { version = "0.11.0-beta.1", optional = true }
|
actix = { version = "0.11.0-beta.1", optional = true }
|
||||||
|
|
||||||
|
@ -53,7 +52,6 @@ bitflags = "1.2"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
copyless = "0.1.4"
|
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
either = "1.5.3"
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
|
@ -87,7 +85,7 @@ flate2 = { version = "1.0.13", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
|
|
|
@ -3,8 +3,7 @@ use std::task::{Context, Poll};
|
||||||
use std::{fmt, mem};
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::{ready, Stream};
|
||||||
use futures_util::ready;
|
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_service::Service;
|
|
||||||
|
|
||||||
/// Service that allows to turn non-clone service to a service with `Clone` impl
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// CloneableService might panic with some creative use of thread local storage.
|
|
||||||
/// See https://github.com/actix/actix-web/issues/1295 for example
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub(crate) struct CloneableService<T>(Rc<RefCell<T>>);
|
|
||||||
|
|
||||||
impl<T> CloneableService<T> {
|
|
||||||
pub(crate) fn new(service: T) -> Self {
|
|
||||||
Self(Rc::new(RefCell::new(service)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for CloneableService<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Service<Req>, Req> Service<Req> for CloneableService<T> {
|
|
||||||
type Response = T::Response;
|
|
||||||
type Error = T::Error;
|
|
||||||
type Future = T::Future;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.0.borrow_mut().poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Req) -> Self::Future {
|
|
||||||
self.0.borrow_mut().call(req)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,14 +3,14 @@ use std::io::{self, Write};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliDecoder;
|
use brotli2::write::BrotliDecoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::error::PayloadError;
|
use crate::error::{BlockingError, PayloadError};
|
||||||
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
||||||
|
|
||||||
const INPLACE: usize = 2049;
|
const INPLACE: usize = 2049;
|
||||||
|
@ -19,7 +19,7 @@ pub struct Decoder<S> {
|
||||||
decoder: Option<ContentDecoder>,
|
decoder: Option<ContentDecoder>,
|
||||||
stream: S,
|
stream: S,
|
||||||
eof: bool,
|
eof: bool,
|
||||||
fut: Option<CpuFuture<(Option<Bytes>, ContentDecoder), io::Error>>,
|
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Decoder<S>
|
impl<S> Decoder<S>
|
||||||
|
@ -80,8 +80,13 @@ where
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
|
let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
|
||||||
Ok(item) => item,
|
Ok(Ok(item)) => item,
|
||||||
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
Ok(Err(e)) => {
|
||||||
|
return Poll::Ready(Some(Err(BlockingError::Error(e).into())))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Poll::Ready(Some(Err(BlockingError::Canceled.into())))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
self.fut.take();
|
self.fut.take();
|
||||||
|
@ -105,7 +110,7 @@ where
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.fut = Some(run(move || {
|
self.fut = Some(spawn_blocking(move || {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
Ok((chunk, decoder))
|
Ok((chunk, decoder))
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::io::{self, Write};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
@ -17,6 +17,7 @@ use crate::http::{HeaderValue, StatusCode};
|
||||||
use crate::{Error, ResponseHead};
|
use crate::{Error, ResponseHead};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
|
use crate::error::BlockingError;
|
||||||
|
|
||||||
const INPLACE: usize = 1024;
|
const INPLACE: usize = 1024;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ pub struct Encoder<B> {
|
||||||
#[pin]
|
#[pin]
|
||||||
body: EncoderBody<B>,
|
body: EncoderBody<B>,
|
||||||
encoder: Option<ContentEncoder>,
|
encoder: Option<ContentEncoder>,
|
||||||
fut: Option<CpuFuture<ContentEncoder, io::Error>>,
|
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: MessageBody> Encoder<B> {
|
impl<B: MessageBody> Encoder<B> {
|
||||||
|
@ -136,8 +137,15 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
|
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
|
let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
|
||||||
Ok(item) => item,
|
Ok(Ok(item)) => item,
|
||||||
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
Ok(Err(e)) => {
|
||||||
|
return Poll::Ready(Some(Err(BlockingError::Error(e).into())))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Poll::Ready(Some(Err(
|
||||||
|
BlockingError::<io::Error>::Canceled.into(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
|
@ -160,7 +168,7 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*this.fut = Some(run(move || {
|
*this.fut = Some(spawn_blocking(move || {
|
||||||
encoder.write(&chunk)?;
|
encoder.write(&chunk)?;
|
||||||
Ok(encoder)
|
Ok(encoder)
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::string::FromUtf8Error;
|
||||||
use std::{fmt, io, result};
|
use std::{fmt, io, result};
|
||||||
|
|
||||||
use actix_codec::{Decoder, Encoder};
|
use actix_codec::{Decoder, Encoder};
|
||||||
pub use actix_threadpool::BlockingError;
|
|
||||||
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
|
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
|
||||||
use actix_utils::timeout::TimeoutError;
|
use actix_utils::timeout::TimeoutError;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
@ -100,10 +99,6 @@ impl fmt::Debug for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl std::error::Error for Error {
|
||||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -190,9 +185,6 @@ impl ResponseError for DeError {
|
||||||
/// `InternalServerError` for `Canceled`
|
/// `InternalServerError` for `Canceled`
|
||||||
impl ResponseError for Canceled {}
|
impl ResponseError for Canceled {}
|
||||||
|
|
||||||
/// `InternalServerError` for `BlockingError`
|
|
||||||
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
|
|
||||||
|
|
||||||
/// Return `BAD_REQUEST` for `Utf8Error`
|
/// Return `BAD_REQUEST` for `Utf8Error`
|
||||||
impl ResponseError for Utf8Error {
|
impl ResponseError for Utf8Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
|
@ -304,33 +296,64 @@ impl From<httparse::Error> for ParseError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur running blocking tasks in thread pool.
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
pub enum BlockingError<E: fmt::Debug> {
|
||||||
|
#[display(fmt = "{:?}", _0)]
|
||||||
|
Error(E),
|
||||||
|
#[display(fmt = "Thread pool is gone")]
|
||||||
|
Canceled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: fmt::Debug> std::error::Error for BlockingError<E> {}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `BlockingError`
|
||||||
|
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
|
||||||
|
|
||||||
#[derive(Display, Debug)]
|
#[derive(Display, Debug)]
|
||||||
/// A set of errors that can occur during payload parsing
|
/// A set of errors that can occur during payload parsing
|
||||||
pub enum PayloadError {
|
pub enum PayloadError {
|
||||||
/// A payload reached EOF, but is not complete.
|
/// A payload reached EOF, but is not complete.
|
||||||
#[display(
|
#[display(
|
||||||
fmt = "A payload reached EOF, but is not complete. With error: {:?}",
|
fmt = "A payload reached EOF, but is not complete. Inner error: {:?}",
|
||||||
_0
|
_0
|
||||||
)]
|
)]
|
||||||
Incomplete(Option<io::Error>),
|
Incomplete(Option<io::Error>),
|
||||||
/// Content encoding stream corruption
|
|
||||||
|
/// Content encoding stream corruption.
|
||||||
#[display(fmt = "Can not decode content-encoding.")]
|
#[display(fmt = "Can not decode content-encoding.")]
|
||||||
EncodingCorrupted,
|
EncodingCorrupted,
|
||||||
/// A payload reached size limit.
|
|
||||||
#[display(fmt = "A payload reached size limit.")]
|
/// Payload reached size limit.
|
||||||
|
#[display(fmt = "Payload reached size limit.")]
|
||||||
Overflow,
|
Overflow,
|
||||||
/// A payload length is unknown.
|
|
||||||
#[display(fmt = "A payload length is unknown.")]
|
/// Payload length is unknown.
|
||||||
|
#[display(fmt = "Payload length is unknown.")]
|
||||||
UnknownLength,
|
UnknownLength,
|
||||||
/// Http2 payload error
|
|
||||||
|
/// HTTP/2 payload error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Http2Payload(h2::Error),
|
Http2Payload(h2::Error),
|
||||||
/// Io error
|
|
||||||
|
/// Generic I/O error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for PayloadError {}
|
impl std::error::Error for PayloadError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
PayloadError::Incomplete(None) => None,
|
||||||
|
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
|
||||||
|
PayloadError::EncodingCorrupted => None,
|
||||||
|
PayloadError::Overflow => None,
|
||||||
|
PayloadError::UnknownLength => None,
|
||||||
|
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
||||||
|
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<h2::Error> for PayloadError {
|
impl From<h2::Error> for PayloadError {
|
||||||
fn from(err: h2::Error) -> Self {
|
fn from(err: h2::Error) -> Self {
|
||||||
|
@ -1009,22 +1032,22 @@ mod tests {
|
||||||
fn test_payload_error() {
|
fn test_payload_error() {
|
||||||
let err: PayloadError =
|
let err: PayloadError =
|
||||||
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||||
assert!(format!("{}", err).contains("ParseError"));
|
assert!(err.to_string().contains("ParseError"));
|
||||||
|
|
||||||
let err = PayloadError::Incomplete(None);
|
let err = PayloadError::Incomplete(None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{}", err),
|
err.to_string(),
|
||||||
"A payload reached EOF, but is not complete. With error: None"
|
"A payload reached EOF, but is not complete. Inner error: None"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from {
|
macro_rules! from {
|
||||||
($from:expr => $error:pat) => {
|
($from:expr => $error:pat) => {
|
||||||
match ParseError::from($from) {
|
match ParseError::from($from) {
|
||||||
e @ $error => {
|
err @ $error => {
|
||||||
assert!(format!("{}", e).len() >= 5);
|
assert!(err.to_string().len() >= 5);
|
||||||
}
|
}
|
||||||
e => unreachable!("{:?}", e),
|
err => unreachable!("{:?}", err),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1067,7 +1090,7 @@ mod tests {
|
||||||
let err = PayloadError::Overflow;
|
let err = PayloadError::Overflow;
|
||||||
let resp_err: &dyn ResponseError = &err;
|
let resp_err: &dyn ResponseError = &err;
|
||||||
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
|
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
|
||||||
assert_eq!(err.to_string(), "A payload reached size limit.");
|
assert_eq!(err.to_string(), "Payload reached size limit.");
|
||||||
let not_err = resp_err.downcast_ref::<ContentTypeError>();
|
let not_err = resp_err.downcast_ref::<ContentTypeError>();
|
||||||
assert!(not_err.is_none());
|
assert!(not_err.is_none());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
io, mem, net,
|
io, mem, net,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,17 +17,14 @@ use bytes::{Buf, BytesMut};
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::cloneable::CloneableService;
|
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
use crate::error::{ParseError, PayloadError};
|
use crate::error::{ParseError, PayloadError};
|
||||||
use crate::httpmessage::HttpMessage;
|
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{
|
use crate::service::HttpFlow;
|
||||||
body::{Body, BodySize, MessageBody, ResponseBody},
|
use crate::OnConnectData;
|
||||||
Extensions,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
||||||
|
@ -78,7 +77,7 @@ where
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
|
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
|
||||||
Upgrade(Pin<Box<U::Future>>),
|
Upgrade(#[pin] U::Future),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = InnerDispatcherProj)]
|
#[pin_project(project = InnerDispatcherProj)]
|
||||||
|
@ -92,10 +91,8 @@ where
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
service: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
expect: CloneableService<X>,
|
on_connect_data: OnConnectData,
|
||||||
upgrade: Option<CloneableService<U>>,
|
|
||||||
on_connect_data: Extensions,
|
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
error: Option<DispatchError>,
|
error: Option<DispatchError>,
|
||||||
|
@ -180,10 +177,8 @@ where
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
stream: T,
|
stream: T,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
service: CloneableService<S>,
|
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
expect: CloneableService<X>,
|
on_connect_data: OnConnectData,
|
||||||
upgrade: Option<CloneableService<U>>,
|
|
||||||
on_connect_data: Extensions,
|
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Dispatcher::with_timeout(
|
Dispatcher::with_timeout(
|
||||||
|
@ -192,9 +187,7 @@ where
|
||||||
config,
|
config,
|
||||||
BytesMut::with_capacity(HW_BUFFER_SIZE),
|
BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
None,
|
None,
|
||||||
service,
|
services,
|
||||||
expect,
|
|
||||||
upgrade,
|
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)
|
)
|
||||||
|
@ -207,10 +200,8 @@ where
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
read_buf: BytesMut,
|
read_buf: BytesMut,
|
||||||
timeout: Option<Sleep>,
|
timeout: Option<Sleep>,
|
||||||
service: CloneableService<S>,
|
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
expect: CloneableService<X>,
|
on_connect_data: OnConnectData,
|
||||||
upgrade: Option<CloneableService<U>>,
|
|
||||||
on_connect_data: Extensions,
|
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let keepalive = config.keep_alive_enabled();
|
let keepalive = config.keep_alive_enabled();
|
||||||
|
@ -239,9 +230,7 @@ where
|
||||||
io: Some(io),
|
io: Some(io),
|
||||||
codec,
|
codec,
|
||||||
read_buf,
|
read_buf,
|
||||||
service,
|
flow: services,
|
||||||
expect,
|
|
||||||
upgrade,
|
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
flags,
|
flags,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
|
@ -298,42 +287,35 @@ where
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<bool, DispatchError> {
|
) -> Result<bool, DispatchError> {
|
||||||
if self.write_buf.is_empty() {
|
let len = self.write_buf.len();
|
||||||
|
if len == 0 {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = self.write_buf.len();
|
|
||||||
let mut written = 0;
|
|
||||||
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
||||||
let mut io = Pin::new(io.as_mut().unwrap());
|
let mut io = Pin::new(io.as_mut().unwrap());
|
||||||
|
|
||||||
|
let mut written = 0;
|
||||||
while written < len {
|
while written < len {
|
||||||
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
||||||
Poll::Ready(Ok(0)) => {
|
Poll::Ready(Ok(0)) => {
|
||||||
return Err(DispatchError::Io(io::Error::new(
|
return Err(DispatchError::Io(io::Error::new(
|
||||||
io::ErrorKind::WriteZero,
|
io::ErrorKind::WriteZero,
|
||||||
"",
|
"",
|
||||||
)));
|
)))
|
||||||
}
|
|
||||||
Poll::Ready(Ok(n)) => {
|
|
||||||
written += n;
|
|
||||||
}
|
}
|
||||||
|
Poll::Ready(Ok(n)) => written += n,
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
if written > 0 {
|
|
||||||
write_buf.advance(written);
|
write_buf.advance(written);
|
||||||
}
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
|
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if written == write_buf.len() {
|
|
||||||
// SAFETY: setting length to 0 is safe
|
// SAFETY: setting length to 0 is safe
|
||||||
// skips one length check vs truncate
|
// skips one length check vs truncate
|
||||||
unsafe { write_buf.set_len(0) }
|
unsafe { write_buf.set_len(0) }
|
||||||
} else {
|
|
||||||
write_buf.advance(written);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
@ -395,7 +377,8 @@ where
|
||||||
Poll::Ready(Ok(req)) => {
|
Poll::Ready(Ok(req)) => {
|
||||||
self.as_mut().send_continue();
|
self.as_mut().send_continue();
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
this.state.set(State::ServiceCall(this.service.call(req)));
|
let fut = this.flow.borrow_mut().service.call(req);
|
||||||
|
this.state.set(State::ServiceCall(fut));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(e)) => {
|
Poll::Ready(Err(e)) => {
|
||||||
|
@ -483,12 +466,14 @@ where
|
||||||
// Handle `EXPECT: 100-Continue` header
|
// Handle `EXPECT: 100-Continue` header
|
||||||
if req.head().expect() {
|
if req.head().expect() {
|
||||||
// set dispatcher state so the future is pinned.
|
// set dispatcher state so the future is pinned.
|
||||||
let task = self.as_mut().project().expect.call(req);
|
let mut this = self.as_mut().project();
|
||||||
self.as_mut().project().state.set(State::ExpectCall(task));
|
let task = this.flow.borrow_mut().expect.call(req);
|
||||||
|
this.state.set(State::ExpectCall(task));
|
||||||
} else {
|
} else {
|
||||||
// the same as above.
|
// the same as above.
|
||||||
let task = self.as_mut().project().service.call(req);
|
let mut this = self.as_mut().project();
|
||||||
self.as_mut().project().state.set(State::ServiceCall(task));
|
let task = this.flow.borrow_mut().service.call(req);
|
||||||
|
this.state.set(State::ServiceCall(task));
|
||||||
};
|
};
|
||||||
|
|
||||||
// eagerly poll the future for once(or twice if expect is resolved immediately).
|
// eagerly poll the future for once(or twice if expect is resolved immediately).
|
||||||
|
@ -499,8 +484,9 @@ where
|
||||||
// expect is resolved. continue loop and poll the service call branch.
|
// expect is resolved. continue loop and poll the service call branch.
|
||||||
Poll::Ready(Ok(req)) => {
|
Poll::Ready(Ok(req)) => {
|
||||||
self.as_mut().send_continue();
|
self.as_mut().send_continue();
|
||||||
let task = self.as_mut().project().service.call(req);
|
let mut this = self.as_mut().project();
|
||||||
self.as_mut().project().state.set(State::ServiceCall(task));
|
let task = this.flow.borrow_mut().service.call(req);
|
||||||
|
this.state.set(State::ServiceCall(task));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// future is pending. return Ok(()) to notify that a new state is
|
// future is pending. return Ok(()) to notify that a new state is
|
||||||
|
@ -568,9 +554,11 @@ where
|
||||||
req.head_mut().peer_addr = *this.peer_addr;
|
req.head_mut().peer_addr = *this.peer_addr;
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
// merge on_connect_ext data into request extensions
|
||||||
req.extensions_mut().drain_from(this.on_connect_data);
|
this.on_connect_data.merge_into(&mut req);
|
||||||
|
|
||||||
if pl == MessageType::Stream && this.upgrade.is_some() {
|
if pl == MessageType::Stream
|
||||||
|
&& this.flow.borrow().upgrade.is_some()
|
||||||
|
{
|
||||||
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -692,15 +680,11 @@ where
|
||||||
if let Some(deadline) =
|
if let Some(deadline) =
|
||||||
this.codec.config().client_disconnect_timer()
|
this.codec.config().client_disconnect_timer()
|
||||||
{
|
{
|
||||||
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut()
|
if let Some(mut timer) =
|
||||||
|
this.ka_timer.as_mut().as_pin_mut()
|
||||||
{
|
{
|
||||||
timer.reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ = this
|
let _ = timer.poll(cx);
|
||||||
.ka_timer
|
|
||||||
.as_mut()
|
|
||||||
.as_pin_mut()
|
|
||||||
.unwrap()
|
|
||||||
.poll(cx);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no shutdown timeout, drop socket
|
// no shutdown timeout, drop socket
|
||||||
|
@ -725,15 +709,14 @@ where
|
||||||
} else if let Some(deadline) =
|
} else if let Some(deadline) =
|
||||||
this.codec.config().keep_alive_expire()
|
this.codec.config().keep_alive_expire()
|
||||||
{
|
{
|
||||||
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
|
if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() {
|
||||||
timer.reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ =
|
let _ = timer.poll(cx);
|
||||||
this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
|
} else if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() {
|
||||||
timer.reset(*this.ka_expire);
|
timer.as_mut().reset(*this.ka_expire);
|
||||||
let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
|
let _ = timer.poll(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending => {}
|
Poll::Pending => {}
|
||||||
|
@ -776,19 +759,12 @@ where
|
||||||
} else {
|
} else {
|
||||||
// flush buffer
|
// flush buffer
|
||||||
inner.as_mut().poll_flush(cx)?;
|
inner.as_mut().poll_flush(cx)?;
|
||||||
if !inner.write_buf.is_empty() || inner.io.is_none() {
|
if !inner.write_buf.is_empty() {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
match Pin::new(inner.project().io)
|
Pin::new(inner.project().io.as_mut().unwrap())
|
||||||
.as_pin_mut()
|
|
||||||
.unwrap()
|
|
||||||
.poll_shutdown(cx)
|
.poll_shutdown(cx)
|
||||||
{
|
.map_err(DispatchError::from)
|
||||||
Poll::Ready(res) => {
|
|
||||||
Poll::Ready(res.map_err(DispatchError::from))
|
|
||||||
}
|
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -834,12 +810,17 @@ where
|
||||||
);
|
);
|
||||||
parts.write_buf = mem::take(inner_p.write_buf);
|
parts.write_buf = mem::take(inner_p.write_buf);
|
||||||
let framed = Framed::from_parts(parts);
|
let framed = Framed::from_parts(parts);
|
||||||
let upgrade =
|
let upgrade = inner_p
|
||||||
inner_p.upgrade.take().unwrap().call((req, framed));
|
.flow
|
||||||
|
.borrow_mut()
|
||||||
|
.upgrade
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.call((req, framed));
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
.project()
|
.project()
|
||||||
.inner
|
.inner
|
||||||
.set(DispatcherState::Upgrade(Box::pin(upgrade)));
|
.set(DispatcherState::Upgrade(upgrade));
|
||||||
return self.poll(cx);
|
return self.poll(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -890,7 +871,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatcherStateProj::Upgrade(fut) => fut.as_mut().poll(cx).map_err(|e| {
|
DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
|
||||||
error!("Upgrade handler error: {}", e);
|
error!("Upgrade handler error: {}", e);
|
||||||
DispatchError::Upgrade
|
DispatchError::Upgrade
|
||||||
}),
|
}),
|
||||||
|
@ -925,7 +906,7 @@ where
|
||||||
buf.reserve(HW_BUFFER_SIZE - remaining);
|
buf.reserve(HW_BUFFER_SIZE - remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
match read(cx, io, buf) {
|
match actix_codec::poll_read_buf(Pin::new(io), cx, buf) {
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
return if read_some { Ok(Some(false)) } else { Ok(None) };
|
return if read_some { Ok(Some(false)) } else { Ok(None) };
|
||||||
}
|
}
|
||||||
|
@ -953,17 +934,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read<T>(
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
io: &mut T,
|
|
||||||
buf: &mut BytesMut,
|
|
||||||
) -> Poll<Result<usize, io::Error>>
|
|
||||||
where
|
|
||||||
T: AsyncRead + Unpin,
|
|
||||||
{
|
|
||||||
actix_codec::poll_read_buf(Pin::new(io), cx, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -1028,13 +998,13 @@ mod tests {
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
|
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
|
||||||
|
|
||||||
|
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
ServiceConfig::default(),
|
ServiceConfig::default(),
|
||||||
CloneableService::new(ok_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
None,
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1068,13 +1038,13 @@ mod tests {
|
||||||
|
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
cfg,
|
cfg,
|
||||||
CloneableService::new(echo_path_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
None,
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1122,13 +1092,13 @@ mod tests {
|
||||||
|
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
cfg,
|
cfg,
|
||||||
CloneableService::new(echo_path_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
None,
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1171,13 +1141,14 @@ mod tests {
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
let mut buf = TestSeqBuffer::empty();
|
let mut buf = TestSeqBuffer::empty();
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
cfg,
|
||||||
CloneableService::new(echo_payload_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
None,
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1242,13 +1213,14 @@ mod tests {
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
let mut buf = TestSeqBuffer::empty();
|
let mut buf = TestSeqBuffer::empty();
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
cfg,
|
||||||
CloneableService::new(echo_path_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
None,
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1301,13 +1273,15 @@ mod tests {
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
let mut buf = TestSeqBuffer::empty();
|
let mut buf = TestSeqBuffer::empty();
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
|
||||||
|
|
||||||
|
let services =
|
||||||
|
HttpFlow::new(ok_service(), ExpectHandler, Some(UpgradeHandler));
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
cfg,
|
||||||
CloneableService::new(ok_service()),
|
services,
|
||||||
CloneableService::new(ExpectHandler),
|
OnConnectData::default(),
|
||||||
Some(CloneableService::new(UpgradeHandler)),
|
|
||||||
Extensions::new(),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -12,12 +13,12 @@ use futures_core::ready;
|
||||||
use futures_util::future::ready;
|
use futures_util::future::ready;
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
use crate::cloneable::CloneableService;
|
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{ConnectCallback, Extensions};
|
use crate::service::HttpFlow;
|
||||||
|
use crate::{ConnectCallback, OnConnectData};
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
@ -299,7 +300,7 @@ where
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: Option<ServiceConfig>,
|
cfg: Option<ServiceConfig>,
|
||||||
_phantom: PhantomData<(T, B)>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
|
||||||
|
@ -337,7 +338,7 @@ where
|
||||||
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.upgrade = Some(upgrade);
|
*this.upgrade = Some(upgrade);
|
||||||
this.fut_ex.set(None);
|
this.fut_upg.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
|
@ -366,9 +367,7 @@ where
|
||||||
X: Service<Request>,
|
X: Service<Request>,
|
||||||
U: Service<(Request, Framed<T, Codec>)>,
|
U: Service<(Request, Framed<T, Codec>)>,
|
||||||
{
|
{
|
||||||
srv: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
expect: CloneableService<X>,
|
|
||||||
upgrade: Option<CloneableService<U>>,
|
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
|
@ -387,15 +386,13 @@ where
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
srv: S,
|
service: S,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
) -> H1ServiceHandler<T, S, B, X, U> {
|
) -> H1ServiceHandler<T, S, B, X, U> {
|
||||||
H1ServiceHandler {
|
H1ServiceHandler {
|
||||||
srv: CloneableService::new(srv),
|
flow: HttpFlow::new(service, expect, upgrade),
|
||||||
expect: CloneableService::new(expect),
|
|
||||||
upgrade: upgrade.map(CloneableService::new),
|
|
||||||
cfg,
|
cfg,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
|
@ -421,7 +418,8 @@ where
|
||||||
type Future = Dispatcher<T, S, B, X, U>;
|
type Future = Dispatcher<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let ready = self
|
let mut flow = self.flow.borrow_mut();
|
||||||
|
let ready = flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -431,8 +429,8 @@ where
|
||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = self
|
let ready = flow
|
||||||
.srv
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
|
@ -442,7 +440,7 @@ where
|
||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = self.upgrade {
|
let ready = if let Some(ref mut upg) = flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
|
@ -463,19 +461,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let mut connect_extensions = Extensions::new();
|
let on_connect_data =
|
||||||
if let Some(ref handler) = self.on_connect_ext {
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
// run on_connect_ext callback, populating connect extensions
|
|
||||||
handler(&io, &mut connect_extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher::new(
|
Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.srv.clone(),
|
self.flow.clone(),
|
||||||
self.expect.clone(),
|
on_connect_data,
|
||||||
self.upgrade.clone(),
|
|
||||||
connect_extensions,
|
|
||||||
addr,
|
addr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{cmp, convert::TryFrom};
|
use std::{cmp, convert::TryFrom};
|
||||||
|
|
||||||
|
@ -16,29 +18,28 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
|
|
||||||
use crate::body::{BodySize, MessageBody, ResponseBody};
|
use crate::body::{BodySize, MessageBody, ResponseBody};
|
||||||
use crate::cloneable::CloneableService;
|
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
use crate::httpmessage::HttpMessage;
|
|
||||||
use crate::message::ResponseHead;
|
use crate::message::ResponseHead;
|
||||||
use crate::payload::Payload;
|
use crate::payload::Payload;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::Extensions;
|
use crate::service::HttpFlow;
|
||||||
|
use crate::OnConnectData;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
|
|
||||||
/// Dispatcher for HTTP/2 protocol.
|
/// Dispatcher for HTTP/2 protocol.
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct Dispatcher<T, S, B>
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
service: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: Extensions,
|
on_connect_data: OnConnectData,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
ka_expire: Instant,
|
ka_expire: Instant,
|
||||||
|
@ -46,7 +47,7 @@ where
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Dispatcher<T, S, B>
|
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
|
@ -55,9 +56,9 @@ where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
service: CloneableService<S>,
|
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: Extensions,
|
on_connect_data: OnConnectData,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
timeout: Option<Sleep>,
|
timeout: Option<Sleep>,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
|
@ -79,7 +80,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
service,
|
flow: services,
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection,
|
||||||
|
@ -91,7 +92,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Future for Dispatcher<T, S, B>
|
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
|
@ -133,11 +134,11 @@ where
|
||||||
head.peer_addr = this.peer_addr;
|
head.peer_addr = this.peer_addr;
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
// merge on_connect_ext data into request extensions
|
||||||
req.extensions_mut().drain_from(&mut this.on_connect_data);
|
this.on_connect_data.merge_into(&mut req);
|
||||||
|
|
||||||
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
||||||
state: ServiceResponseState::ServiceCall(
|
state: ServiceResponseState::ServiceCall(
|
||||||
this.service.call(req),
|
this.flow.borrow_mut().service.call(req),
|
||||||
Some(res),
|
Some(res),
|
||||||
),
|
),
|
||||||
config: this.config.clone(),
|
config: this.config.clone(),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -17,12 +18,12 @@ use h2::server::{self, Handshake};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
use crate::cloneable::CloneableService;
|
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{ConnectCallback, Extensions};
|
use crate::service::HttpFlow;
|
||||||
|
use crate::{ConnectCallback, OnConnectData};
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
|
@ -248,7 +249,7 @@ pub struct H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
{
|
{
|
||||||
srv: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, (), ()>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
|
@ -265,12 +266,12 @@ where
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
srv: S,
|
service: S,
|
||||||
) -> H2ServiceHandler<T, S, B> {
|
) -> H2ServiceHandler<T, S, B> {
|
||||||
H2ServiceHandler {
|
H2ServiceHandler {
|
||||||
|
flow: HttpFlow::new(service, (), None),
|
||||||
cfg,
|
cfg,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
srv: CloneableService::new(srv),
|
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +291,7 @@ where
|
||||||
type Future = H2ServiceHandlerResponse<T, S, B>;
|
type Future = H2ServiceHandlerResponse<T, S, B>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.srv.poll_ready(cx).map_err(|e| {
|
self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
error!("Service readiness error: {:?}", e);
|
error!("Service readiness error: {:?}", e);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(e)
|
||||||
|
@ -298,18 +299,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let mut connect_extensions = Extensions::new();
|
let on_connect_data =
|
||||||
if let Some(ref handler) = self.on_connect_ext {
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
// run on_connect_ext callback, populating connect extensions
|
|
||||||
handler(&io, &mut connect_extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
H2ServiceHandlerResponse {
|
H2ServiceHandlerResponse {
|
||||||
state: State::Handshake(
|
state: State::Handshake(
|
||||||
Some(self.srv.clone()),
|
Some(self.flow.clone()),
|
||||||
Some(self.cfg.clone()),
|
Some(self.cfg.clone()),
|
||||||
addr,
|
addr,
|
||||||
Some(connect_extensions),
|
on_connect_data,
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -321,12 +319,12 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
Incoming(Dispatcher<T, S, B>),
|
Incoming(Dispatcher<T, S, B, (), ()>),
|
||||||
Handshake(
|
Handshake(
|
||||||
Option<CloneableService<S>>,
|
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
Option<Extensions>,
|
OnConnectData,
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -365,10 +363,11 @@ where
|
||||||
ref mut handshake,
|
ref mut handshake,
|
||||||
) => match ready!(Pin::new(handshake).poll(cx)) {
|
) => match ready!(Pin::new(handshake).poll(cx)) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
|
let on_connect_data = std::mem::take(on_connect_data);
|
||||||
self.state = State::Incoming(Dispatcher::new(
|
self.state = State::Incoming(Dispatcher::new(
|
||||||
srv.take().unwrap(),
|
srv.take().unwrap(),
|
||||||
conn,
|
conn,
|
||||||
on_connect_data.take().unwrap(),
|
on_connect_data,
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
None,
|
None,
|
||||||
*peer_addr,
|
*peer_addr,
|
||||||
|
|
|
@ -19,7 +19,6 @@ mod macros;
|
||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
mod cloneable;
|
|
||||||
mod config;
|
mod config;
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
|
@ -73,11 +72,49 @@ pub mod http {
|
||||||
pub use crate::message::ConnectionType;
|
pub use crate::message::ConnectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Http protocol
|
/// A major HTTP protocol version.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
Http1,
|
Http1,
|
||||||
Http2,
|
Http2,
|
||||||
|
Http3,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||||
|
|
||||||
|
/// Container for data that extract with ConnectCallback.
|
||||||
|
///
|
||||||
|
/// # Implementation Details
|
||||||
|
/// Uses Option to reduce necessary allocations when merging with request extensions.
|
||||||
|
pub(crate) struct OnConnectData(Option<Extensions>);
|
||||||
|
|
||||||
|
impl Default for OnConnectData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnConnectData {
|
||||||
|
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
||||||
|
pub(crate) fn from_io<T>(
|
||||||
|
io: &T,
|
||||||
|
on_connect_ext: Option<&ConnectCallback<T>>,
|
||||||
|
) -> Self {
|
||||||
|
let ext = on_connect_ext.map(|handler| {
|
||||||
|
let mut extensions = Extensions::new();
|
||||||
|
handler(io, &mut extensions);
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
Self(ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge self into given request's extensions.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn merge_into(&mut self, req: &mut Request) {
|
||||||
|
if let Some(ref mut ext) = self.0 {
|
||||||
|
req.head.extensions.get_mut().drain_from(ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::net;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use copyless::BoxHelper;
|
|
||||||
|
|
||||||
use crate::extensions::Extensions;
|
use crate::extensions::Extensions;
|
||||||
use crate::header::HeaderMap;
|
use crate::header::HeaderMap;
|
||||||
|
@ -35,7 +34,9 @@ bitflags! {
|
||||||
pub trait Head: Default + 'static {
|
pub trait Head: Default + 'static {
|
||||||
fn clear(&mut self);
|
fn clear(&mut self);
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self>;
|
fn with_pool<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&MessagePool<Self>) -> R;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -70,8 +71,11 @@ impl Head for RequestHead {
|
||||||
self.extensions.get_mut().clear();
|
self.extensions.get_mut().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self> {
|
fn with_pool<F, R>(f: F) -> R
|
||||||
REQUEST_POOL.with(|p| *p)
|
where
|
||||||
|
F: FnOnce(&MessagePool<Self>) -> R,
|
||||||
|
{
|
||||||
|
REQUEST_POOL.with(|p| f(p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +349,7 @@ pub struct Message<T: Head> {
|
||||||
impl<T: Head> Message<T> {
|
impl<T: Head> Message<T> {
|
||||||
/// Get new message from the pool of objects
|
/// Get new message from the pool of objects
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
T::pool().get_message()
|
T::with_pool(|p| p.get_message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +378,7 @@ impl<T: Head> std::ops::DerefMut for Message<T> {
|
||||||
impl<T: Head> Drop for Message<T> {
|
impl<T: Head> Drop for Message<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if Rc::strong_count(&self.head) == 1 {
|
if Rc::strong_count(&self.head) == 1 {
|
||||||
T::pool().release(self.head.clone());
|
T::with_pool(|p| p.release(self.head.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,18 +431,17 @@ pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
||||||
/// Request's objects pool
|
/// Request's objects pool
|
||||||
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
||||||
|
|
||||||
thread_local!(static REQUEST_POOL: &'static MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
thread_local!(static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
||||||
thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create());
|
thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create());
|
||||||
|
|
||||||
impl<T: Head> MessagePool<T> {
|
impl<T: Head> MessagePool<T> {
|
||||||
fn create() -> &'static MessagePool<T> {
|
fn create() -> MessagePool<T> {
|
||||||
let pool = MessagePool(RefCell::new(Vec::with_capacity(128)));
|
MessagePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
Box::leak(Box::new(pool))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&'static self) -> Message<T> {
|
fn get_message(&self) -> Message<T> {
|
||||||
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
||||||
// Message is put in pool only when it's the last copy.
|
// Message is put in pool only when it's the last copy.
|
||||||
// which means it's guaranteed to be unique when popped out.
|
// which means it's guaranteed to be unique when popped out.
|
||||||
|
@ -464,14 +467,13 @@ impl<T: Head> MessagePool<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxedResponsePool {
|
impl BoxedResponsePool {
|
||||||
fn create() -> &'static BoxedResponsePool {
|
fn create() -> BoxedResponsePool {
|
||||||
let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128)));
|
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
Box::leak(Box::new(pool))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead {
|
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||||
head.reason = None;
|
head.reason = None;
|
||||||
head.status = status;
|
head.status = status;
|
||||||
|
@ -480,17 +482,17 @@ impl BoxedResponsePool {
|
||||||
BoxedResponseHead { head: Some(head) }
|
BoxedResponseHead { head: Some(head) }
|
||||||
} else {
|
} else {
|
||||||
BoxedResponseHead {
|
BoxedResponseHead {
|
||||||
head: Some(Box::alloc().init(ResponseHead::new(status))),
|
head: Some(Box::new(ResponseHead::new(status))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Release request instance
|
/// Release request instance
|
||||||
fn release(&self, msg: Box<ResponseHead>) {
|
fn release(&self, mut msg: Box<ResponseHead>) {
|
||||||
let v = &mut self.0.borrow_mut();
|
let v = &mut self.0.borrow_mut();
|
||||||
if v.len() < 128 {
|
if v.len() < 128 {
|
||||||
msg.extensions.borrow_mut().clear();
|
msg.extensions.get_mut().clear();
|
||||||
v.push(msg);
|
v.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -481,15 +481,14 @@ impl ResponseBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set response content type
|
/// Set response content type.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
HeaderValue: TryFrom<V>,
|
V: IntoHeaderValue,
|
||||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderValue::try_from(value) {
|
match value.try_into() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||||
}
|
}
|
||||||
|
@ -802,7 +801,7 @@ impl From<ResponseBuilder> for Response {
|
||||||
impl From<&'static str> for Response {
|
impl From<&'static str> for Response {
|
||||||
fn from(val: &'static str) -> Self {
|
fn from(val: &'static str) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -810,7 +809,7 @@ impl From<&'static str> for Response {
|
||||||
impl From<&'static [u8]> for Response {
|
impl From<&'static [u8]> for Response {
|
||||||
fn from(val: &'static [u8]) -> Self {
|
fn from(val: &'static [u8]) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -818,7 +817,7 @@ impl From<&'static [u8]> for Response {
|
||||||
impl From<String> for Response {
|
impl From<String> for Response {
|
||||||
fn from(val: String) -> Self {
|
fn from(val: String) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -826,7 +825,7 @@ impl From<String> for Response {
|
||||||
impl<'a> From<&'a String> for Response {
|
impl<'a> From<&'a String> for Response {
|
||||||
fn from(val: &'a String) -> Self {
|
fn from(val: &'a String) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -834,7 +833,7 @@ impl<'a> From<&'a String> for Response {
|
||||||
impl From<Bytes> for Response {
|
impl From<Bytes> for Response {
|
||||||
fn from(val: Bytes) -> Self {
|
fn from(val: Bytes) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -842,7 +841,7 @@ impl From<Bytes> for Response {
|
||||||
impl From<BytesMut> for Response {
|
impl From<BytesMut> for Response {
|
||||||
fn from(val: BytesMut) -> Self {
|
fn from(val: BytesMut) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
@ -8,18 +9,16 @@ use actix_rt::net::TcpStream;
|
||||||
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Future};
|
use futures_core::{ready, Future};
|
||||||
use futures_util::future::ok;
|
|
||||||
use h2::server::{self, Handshake};
|
use h2::server::{self, Handshake};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
use crate::builder::HttpServiceBuilder;
|
use crate::builder::HttpServiceBuilder;
|
||||||
use crate::cloneable::CloneableService;
|
|
||||||
use crate::config::{KeepAlive, ServiceConfig};
|
use crate::config::{KeepAlive, ServiceConfig};
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol};
|
use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol};
|
||||||
|
|
||||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||||
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||||
|
@ -175,9 +174,9 @@ where
|
||||||
Error = DispatchError,
|
Error = DispatchError,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(|io: TcpStream| {
|
pipeline_factory(|io: TcpStream| async {
|
||||||
let peer_addr = io.peer_addr().ok();
|
let peer_addr = io.peer_addr().ok();
|
||||||
ok((io, Protocol::Http1, peer_addr))
|
Ok((io, Protocol::Http1, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self)
|
.and_then(self)
|
||||||
}
|
}
|
||||||
|
@ -227,7 +226,7 @@ mod openssl {
|
||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: SslStream<TcpStream>| {
|
.and_then(|io: SslStream<TcpStream>| async {
|
||||||
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
|
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
|
||||||
if protos.windows(2).any(|window| window == b"h2") {
|
if protos.windows(2).any(|window| window == b"h2") {
|
||||||
Protocol::Http2
|
Protocol::Http2
|
||||||
|
@ -238,7 +237,7 @@ mod openssl {
|
||||||
Protocol::Http1
|
Protocol::Http1
|
||||||
};
|
};
|
||||||
let peer_addr = io.get_ref().peer_addr().ok();
|
let peer_addr = io.get_ref().peer_addr().ok();
|
||||||
ok((io, proto, peer_addr))
|
Ok((io, proto, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
|
@ -295,7 +294,7 @@ mod rustls {
|
||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: TlsStream<TcpStream>| {
|
.and_then(|io: TlsStream<TcpStream>| async {
|
||||||
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
|
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
|
||||||
if protos.windows(2).any(|window| window == b"h2") {
|
if protos.windows(2).any(|window| window == b"h2") {
|
||||||
Protocol::Http2
|
Protocol::Http2
|
||||||
|
@ -306,7 +305,7 @@ mod rustls {
|
||||||
Protocol::Http1
|
Protocol::Http1
|
||||||
};
|
};
|
||||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||||
ok((io, proto, peer_addr))
|
Ok((io, proto, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
|
@ -371,7 +370,7 @@ where
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_phantom: PhantomData<(T, B)>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
|
||||||
|
@ -413,7 +412,7 @@ where
|
||||||
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.upgrade = Some(upgrade);
|
*this.upgrade = Some(upgrade);
|
||||||
this.fut_ex.set(None);
|
this.fut_upg.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
|
@ -441,14 +440,29 @@ where
|
||||||
X: Service<Request>,
|
X: Service<Request>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>)>,
|
U: Service<(Request, Framed<T, h1::Codec>)>,
|
||||||
{
|
{
|
||||||
srv: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
expect: CloneableService<X>,
|
|
||||||
upgrade: Option<CloneableService<U>>,
|
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A collection of services that describe an HTTP request flow.
|
||||||
|
pub(super) struct HttpFlow<S, X, U> {
|
||||||
|
pub(super) service: S,
|
||||||
|
pub(super) expect: X,
|
||||||
|
pub(super) upgrade: Option<U>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, X, U> HttpFlow<S, X, U> {
|
||||||
|
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<RefCell<Self>> {
|
||||||
|
Rc::new(RefCell::new(Self {
|
||||||
|
service,
|
||||||
|
expect,
|
||||||
|
upgrade,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
|
@ -463,7 +477,7 @@ where
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
srv: S,
|
service: S,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
|
@ -471,9 +485,7 @@ where
|
||||||
HttpServiceHandler {
|
HttpServiceHandler {
|
||||||
cfg,
|
cfg,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
srv: CloneableService::new(srv),
|
flow: HttpFlow::new(service, expect, upgrade),
|
||||||
expect: CloneableService::new(expect),
|
|
||||||
upgrade: upgrade.map(CloneableService::new),
|
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,7 +510,8 @@ where
|
||||||
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let ready = self
|
let mut flow = self.flow.borrow_mut();
|
||||||
|
let ready = flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -508,8 +521,8 @@ where
|
||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = self
|
let ready = flow
|
||||||
.srv
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
|
@ -519,7 +532,7 @@ where
|
||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = self.upgrade {
|
let ready = if let Some(ref mut upg) = flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
|
@ -543,19 +556,16 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
||||||
) -> Self::Future {
|
) -> Self::Future {
|
||||||
let mut connect_extensions = Extensions::new();
|
let on_connect_data =
|
||||||
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
if let Some(ref handler) = self.on_connect_ext {
|
|
||||||
handler(&io, &mut connect_extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
state: State::H2Handshake(Some((
|
state: State::H2Handshake(Some((
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.srv.clone(),
|
self.flow.clone(),
|
||||||
connect_extensions,
|
on_connect_data,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
|
@ -564,13 +574,13 @@ where
|
||||||
state: State::H1(h1::Dispatcher::new(
|
state: State::H1(h1::Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.srv.clone(),
|
self.flow.clone(),
|
||||||
self.expect.clone(),
|
on_connect_data,
|
||||||
self.upgrade.clone(),
|
|
||||||
connect_extensions,
|
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,13 +599,13 @@ where
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
||||||
H2(#[pin] Dispatcher<T, S, B>),
|
H2(#[pin] Dispatcher<T, S, B, X, U>),
|
||||||
H2Handshake(
|
H2Handshake(
|
||||||
Option<(
|
Option<(
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
CloneableService<S>,
|
Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
Extensions,
|
OnConnectData,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
|
@ -634,45 +644,16 @@ where
|
||||||
{
|
{
|
||||||
type Output = Result<(), DispatchError>;
|
type Output = Result<(), DispatchError>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
self.project().state.poll(cx)
|
match self.as_mut().project().state.project() {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, S, B, X, U> State<T, S, B, X, U>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
S: Service<Request>,
|
|
||||||
S::Error: Into<Error> + 'static,
|
|
||||||
S::Response: Into<Response<B>> + 'static,
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
X: Service<Request, Response = Request>,
|
|
||||||
X::Error: Into<Error>,
|
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
|
||||||
U::Error: fmt::Display,
|
|
||||||
{
|
|
||||||
fn poll(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), DispatchError>> {
|
|
||||||
match self.as_mut().project() {
|
|
||||||
StateProj::H1(disp) => disp.poll(cx),
|
StateProj::H1(disp) => disp.poll(cx),
|
||||||
StateProj::H2(disp) => disp.poll(cx),
|
StateProj::H2(disp) => disp.poll(cx),
|
||||||
StateProj::H2Handshake(ref mut data) => {
|
StateProj::H2Handshake(data) => {
|
||||||
let conn = if let Some(ref mut item) = data {
|
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||||
match Pin::new(&mut item.0).poll(cx) {
|
Ok(conn) => {
|
||||||
Poll::Ready(Ok(conn)) => conn,
|
let (_, cfg, srv, on_connect_data, peer_addr) =
|
||||||
Poll::Ready(Err(err)) => {
|
data.take().unwrap();
|
||||||
trace!("H2 handshake error: {}", err);
|
self.as_mut().project().state.set(State::H2(Dispatcher::new(
|
||||||
return Poll::Ready(Err(err.into()));
|
|
||||||
}
|
|
||||||
Poll::Pending => return Poll::Pending,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
};
|
|
||||||
let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap();
|
|
||||||
self.set(State::H2(Dispatcher::new(
|
|
||||||
srv,
|
srv,
|
||||||
conn,
|
conn,
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
|
@ -682,6 +663,12 @@ where
|
||||||
)));
|
)));
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
trace!("H2 handshake error: {}", err);
|
||||||
|
Poll::Ready(Err(err.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl Codec {
|
||||||
|
|
||||||
/// Set max frame size.
|
/// Set max frame size.
|
||||||
///
|
///
|
||||||
/// By default max size is set to 64kb.
|
/// By default max size is set to 64kB.
|
||||||
pub fn max_size(mut self, size: usize) -> Self {
|
pub fn max_size(mut self, size: usize) -> Self {
|
||||||
self.max_size = size;
|
self.max_size = size;
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::convert::{From, Into};
|
use std::convert::{From, Into};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use self::OpCode::*;
|
|
||||||
/// Operation codes as part of RFC6455.
|
/// Operation codes as part of RFC6455.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
pub enum OpCode {
|
pub enum OpCode {
|
||||||
|
@ -29,6 +28,7 @@ pub enum OpCode {
|
||||||
|
|
||||||
impl fmt::Display for OpCode {
|
impl fmt::Display for OpCode {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use self::OpCode::*;
|
||||||
match *self {
|
match *self {
|
||||||
Continue => write!(f, "CONTINUE"),
|
Continue => write!(f, "CONTINUE"),
|
||||||
Text => write!(f, "TEXT"),
|
Text => write!(f, "TEXT"),
|
||||||
|
@ -41,9 +41,10 @@ impl fmt::Display for OpCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u8> for OpCode {
|
impl From<OpCode> for u8 {
|
||||||
fn into(self) -> u8 {
|
fn from(op: OpCode) -> u8 {
|
||||||
match self {
|
use self::OpCode::*;
|
||||||
|
match op {
|
||||||
Continue => 0,
|
Continue => 0,
|
||||||
Text => 1,
|
Text => 1,
|
||||||
Binary => 2,
|
Binary => 2,
|
||||||
|
@ -60,6 +61,7 @@ impl Into<u8> for OpCode {
|
||||||
|
|
||||||
impl From<u8> for OpCode {
|
impl From<u8> for OpCode {
|
||||||
fn from(byte: u8) -> OpCode {
|
fn from(byte: u8) -> OpCode {
|
||||||
|
use self::OpCode::*;
|
||||||
match byte {
|
match byte {
|
||||||
0 => Continue,
|
0 => Continue,
|
||||||
1 => Text,
|
1 => Text,
|
||||||
|
@ -72,7 +74,6 @@ impl From<u8> for OpCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use self::CloseCode::*;
|
|
||||||
/// Status code used to indicate why an endpoint is closing the `WebSocket`
|
/// Status code used to indicate why an endpoint is closing the `WebSocket`
|
||||||
/// connection.
|
/// connection.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
|
@ -138,9 +139,10 @@ pub enum CloseCode {
|
||||||
Other(u16),
|
Other(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u16> for CloseCode {
|
impl From<CloseCode> for u16 {
|
||||||
fn into(self) -> u16 {
|
fn from(code: CloseCode) -> u16 {
|
||||||
match self {
|
use self::CloseCode::*;
|
||||||
|
match code {
|
||||||
Normal => 1000,
|
Normal => 1000,
|
||||||
Away => 1001,
|
Away => 1001,
|
||||||
Protocol => 1002,
|
Protocol => 1002,
|
||||||
|
@ -161,6 +163,7 @@ impl Into<u16> for CloseCode {
|
||||||
|
|
||||||
impl From<u16> for CloseCode {
|
impl From<u16> for CloseCode {
|
||||||
fn from(code: u16) -> CloseCode {
|
fn from(code: u16) -> CloseCode {
|
||||||
|
use self::CloseCode::*;
|
||||||
match code {
|
match code {
|
||||||
1000 => Normal,
|
1000 => Normal,
|
||||||
1001 => Away,
|
1001 => Away,
|
||||||
|
@ -185,6 +188,7 @@ impl From<u16> for CloseCode {
|
||||||
pub struct CloseReason {
|
pub struct CloseReason {
|
||||||
/// Exit code
|
/// Exit code
|
||||||
pub code: CloseCode,
|
pub code: CloseCode,
|
||||||
|
|
||||||
/// Optional description of the exit code
|
/// Optional description of the exit code
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* Fix multipart consuming payload before header checks #1513
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.1 - 2021-01-07
|
||||||
|
* Fix multipart consuming payload before header checks. [#1513]
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1513]: https://github.com/actix/actix-web/pull/1513
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 0.3.0 - 2020-09-11
|
||||||
* No significant changes from `3.0.0-beta.2`.
|
* No significant changes from `0.3.0-beta.2`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.2 - 2020-09-10
|
## 0.3.0-beta.2 - 2020-09-10
|
||||||
* Update `actix-*` dependencies to latest versions.
|
* Update `actix-*` dependencies to latest versions.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.3.0"
|
version = "0.4.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart support for actix web framework."
|
description = "Multipart support for actix web framework."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -16,11 +16,11 @@ name = "actix_multipart"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "3.0.0", default-features = false }
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -28,5 +28,5 @@ mime = "0.3"
|
||||||
twoway = "0.2"
|
twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
* Update `pin-project` to `1.0`.
|
* Update `pin-project` to `1.0`.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
* `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
|
* `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
|
||||||
|
@ -8,6 +11,7 @@
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 3.0.0 - 2020-09-11
|
||||||
* No significant changes from `3.0.0-beta.2`.
|
* No significant changes from `3.0.0-beta.2`.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "3.0.0"
|
version = "4.0.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for actix web framework."
|
description = "Actix actors support for actix web framework."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -18,8 +18,8 @@ path = "src/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = "0.11.0-beta.1"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
actix-web = { version = "3.0.0", default-features = false }
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
|
@ -28,6 +28,6 @@ pin-project = "1.0.0"
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
|
|
@ -19,8 +19,8 @@ syn = { version = "1", features = ["full", "parsing"] }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
actix-web = "3.0.0"
|
actix-web = "4.0.0-beta.1"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
### Changed
|
### Changed
|
||||||
* Update `rand` to `0.8`
|
* Update `rand` to `0.8`
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "2.0.3"
|
version = "3.0.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -38,14 +38,14 @@ compress = ["actix-http/compress"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.3"
|
||||||
actix-http = "2.2.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2.0.0-beta.2"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
log =" 0.4"
|
log =" 0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
@ -61,9 +61,9 @@ rust-tls = { version = "0.19.0", package = "rustls", optional = true, features =
|
||||||
# TODO: actix is temporary added as dev dep for actix-macro reason.
|
# TODO: actix is temporary added as dev dep for actix-macro reason.
|
||||||
# Can be removed when it does not impact tests.
|
# Can be removed when it does not impact tests.
|
||||||
actix = "0.11.0-beta.1"
|
actix = "0.11.0-beta.1"
|
||||||
actix-web = { version = "3.0.0", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-http = { version = "2.0.0", features = ["openssl"] }
|
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! ## Making a GET request
|
//! ## Making a GET request
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
//! let mut client = awc::Client::default();
|
//! let mut client = awc::Client::default();
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
//!
|
//!
|
||||||
//! ### Raw body contents
|
//! ### Raw body contents
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
//! let mut client = awc::Client::default();
|
//! let mut client = awc::Client::default();
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
//!
|
//!
|
||||||
//! ### Forms
|
//! ### Forms
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
//! let params = [("foo", "bar"), ("baz", "quux")];
|
//! let params = [("foo", "bar"), ("baz", "quux")];
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
//!
|
//!
|
||||||
//! ### JSON
|
//! ### JSON
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
//! let request = serde_json::json!({
|
//! let request = serde_json::json!({
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
//!
|
//!
|
||||||
//! ## WebSocket support
|
//! ## WebSocket support
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
||||||
|
|
|
@ -184,7 +184,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Change max size of payload. By default max size is 256kB
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
fut.limit = limit;
|
fut.limit = limit;
|
||||||
|
@ -276,7 +276,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 64Kb
|
/// Change max size of payload. By default max size is 64kB
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
fut.limit = limit;
|
fut.limit = limit;
|
||||||
|
|
|
@ -33,18 +33,18 @@ pub(crate) enum PrepForSendingError {
|
||||||
Http(HttpError),
|
Http(HttpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<FreezeRequestError> for PrepForSendingError {
|
impl From<PrepForSendingError> for FreezeRequestError {
|
||||||
fn into(self) -> FreezeRequestError {
|
fn from(err: PrepForSendingError) -> FreezeRequestError {
|
||||||
match self {
|
match err {
|
||||||
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
|
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
|
||||||
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
|
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<SendRequestError> for PrepForSendingError {
|
impl From<PrepForSendingError> for SendRequestError {
|
||||||
fn into(self) -> SendRequestError {
|
fn from(err: PrepForSendingError) -> SendRequestError {
|
||||||
match self {
|
match err {
|
||||||
PrepForSendingError::Url(e) => SendRequestError::Url(e),
|
PrepForSendingError::Url(e) => SendRequestError::Url(e),
|
||||||
PrepForSendingError::Http(e) => SendRequestError::Http(e),
|
PrepForSendingError::Http(e) => SendRequestError::Http(e),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```no_run
|
||||||
//! use awc::{Client, ws};
|
//! use awc::{Client, ws};
|
||||||
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
||||||
//!
|
//!
|
||||||
|
@ -147,7 +147,7 @@ impl WebsocketsRequest {
|
||||||
|
|
||||||
/// Set max frame size
|
/// Set max frame size
|
||||||
///
|
///
|
||||||
/// By default max size is set to 64kb
|
/// By default max size is set to 64kB
|
||||||
pub fn max_frame_size(mut self, size: usize) -> Self {
|
pub fn max_frame_size(mut self, size: usize) -> Self {
|
||||||
self.max_size = size;
|
self.max_size = size;
|
||||||
self
|
self
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use actix_http::Response;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::test::TestRequest;
|
||||||
|
use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
|
||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use futures_util::future::{ready, Either, Ready};
|
||||||
|
|
||||||
|
// responder simulate the old responder trait.
|
||||||
|
trait FutureResponder {
|
||||||
|
type Error;
|
||||||
|
type Future: Future<Output = Result<HttpResponse, Self::Error>>;
|
||||||
|
|
||||||
|
fn future_respond_to(self, req: &HttpRequest) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a simple option responder type.
|
||||||
|
struct OptionResponder<T>(Option<T>);
|
||||||
|
|
||||||
|
// a simple wrapper type around string
|
||||||
|
struct StringResponder(String);
|
||||||
|
|
||||||
|
impl FutureResponder for StringResponder {
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Ready<Result<Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn future_respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||||
|
// this is default builder for string response in both new and old responder trait.
|
||||||
|
ready(Ok(Response::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FutureResponder for OptionResponder<T>
|
||||||
|
where
|
||||||
|
T: FutureResponder,
|
||||||
|
T::Future: Future<Output = Result<Response, Error>>,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Either<T::Future, Ready<Result<HttpResponse, Self::Error>>>;
|
||||||
|
|
||||||
|
fn future_respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||||
|
match self.0 {
|
||||||
|
Some(t) => Either::Left(t.future_respond_to(req)),
|
||||||
|
None => Either::Right(ready(Err(error::ErrorInternalServerError("err")))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for StringResponder {
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
|
Response::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Responder> Responder for OptionResponder<T> {
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
|
match self.0 {
|
||||||
|
Some(t) => t.respond_to(req),
|
||||||
|
None => Response::from_error(error::ErrorInternalServerError("err")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn future_responder(c: &mut Criterion) {
|
||||||
|
let rt = actix_rt::System::new("test");
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
|
c.bench_function("future_responder", move |b| {
|
||||||
|
b.iter_custom(|_| {
|
||||||
|
let futs = (0..100_000).map(|_| async {
|
||||||
|
StringResponder(String::from("Hello World!!"))
|
||||||
|
.future_respond_to(&req)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
let futs = futures_util::future::join_all(futs);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let _res = rt.block_on(async { futs.await });
|
||||||
|
|
||||||
|
start.elapsed()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn responder(c: &mut Criterion) {
|
||||||
|
let rt = actix_rt::System::new("test");
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
c.bench_function("responder", move |b| {
|
||||||
|
b.iter_custom(|_| {
|
||||||
|
let responders =
|
||||||
|
(0..100_000).map(|_| StringResponder(String::from("Hello World!!")));
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let _res = rt.block_on(async {
|
||||||
|
// don't need runtime block on but to be fair.
|
||||||
|
responders.map(|r| r.respond_to(&req)).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
start.elapsed()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(responder_bench, future_responder, responder);
|
||||||
|
criterion_main!(responder_bench);
|
|
@ -4,7 +4,7 @@
|
||||||
//! For an example of extracting a client TLS certificate, see:
|
//! For an example of extracting a client TLS certificate, see:
|
||||||
//! <https://github.com/actix/examples/tree/HEAD/rustls-client-cert>
|
//! <https://github.com/actix/examples/tree/HEAD/rustls-client-cert>
|
||||||
|
|
||||||
use std::{any::Any, env, io, net::SocketAddr};
|
use std::{any::Any, io, net::SocketAddr};
|
||||||
|
|
||||||
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
|
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
|
||||||
|
|
||||||
|
@ -36,11 +36,7 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
if env::var("RUST_LOG").is_err() {
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
env::set_var("RUST_LOG", "info");
|
|
||||||
}
|
|
||||||
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
|
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
|
||||||
.on_connect(get_conn_info)
|
.on_connect(get_conn_info)
|
||||||
|
|
|
@ -22,8 +22,7 @@ async fn no_params() -> &'static str {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
|
@ -527,11 +527,12 @@ where
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
|
T::Future: 'static,
|
||||||
{
|
{
|
||||||
fn into_factory(self) -> AppInit<T, B> {
|
fn into_factory(self) -> AppInit<T, B> {
|
||||||
AppInit {
|
AppInit {
|
||||||
data: self.data.into_boxed_slice().into(),
|
data_factories: self.data.into_boxed_slice().into(),
|
||||||
data_factories: self.data_factories.into_boxed_slice().into(),
|
async_data_factories: self.data_factories.into_boxed_slice().into(),
|
||||||
endpoint: self.endpoint,
|
endpoint: self.endpoint,
|
||||||
services: Rc::new(RefCell::new(self.services)),
|
services: Rc::new(RefCell::new(self.services)),
|
||||||
external: RefCell::new(self.external),
|
external: RefCell::new(self.external),
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::Poll;
|
||||||
|
|
||||||
use actix_http::{Extensions, Request, Response};
|
use actix_http::{Extensions, Request, Response};
|
||||||
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
|
use actix_router::{Path, ResourceDef, Router, Url};
|
||||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
||||||
use actix_service::{fn_service, Service, ServiceFactory};
|
use actix_service::{fn_service, Service, ServiceFactory};
|
||||||
use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture};
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::config::{AppConfig, AppService};
|
use crate::config::{AppConfig, AppService};
|
||||||
use crate::data::{DataFactory, FnDataFactory};
|
use crate::data::{DataFactory, FnDataFactory};
|
||||||
|
@ -22,7 +20,6 @@ use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse};
|
||||||
type Guards = Vec<Box<dyn Guard>>;
|
type Guards = Vec<Box<dyn Guard>>;
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||||
type BoxResponse = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
|
|
||||||
|
|
||||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||||
/// It also executes data factories.
|
/// It also executes data factories.
|
||||||
|
@ -38,8 +35,8 @@ where
|
||||||
{
|
{
|
||||||
pub(crate) endpoint: T,
|
pub(crate) endpoint: T,
|
||||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||||
pub(crate) data: Rc<[Box<dyn DataFactory>]>,
|
pub(crate) data_factories: Rc<[Box<dyn DataFactory>]>,
|
||||||
pub(crate) data_factories: Rc<[FnDataFactory]>,
|
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
||||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
|
@ -55,24 +52,27 @@ where
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
>,
|
>,
|
||||||
|
T::Future: 'static,
|
||||||
{
|
{
|
||||||
type Response = ServiceResponse<B>;
|
type Response = ServiceResponse<B>;
|
||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
type Config = AppConfig;
|
type Config = AppConfig;
|
||||||
type Service = AppInitService<T::Service, B>;
|
type Service = AppInitService<T::Service, B>;
|
||||||
type InitError = T::InitError;
|
type InitError = T::InitError;
|
||||||
type Future = AppInitResult<T, B>;
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, config: AppConfig) -> Self::Future {
|
fn new_service(&self, config: AppConfig) -> Self::Future {
|
||||||
// update resource default service
|
// set AppService's default service to 404 NotFound
|
||||||
|
// if no user defined default service exists.
|
||||||
let default = self.default.clone().unwrap_or_else(|| {
|
let default = self.default.clone().unwrap_or_else(|| {
|
||||||
Rc::new(boxed::factory(fn_service(|req: ServiceRequest| {
|
Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||||
ok(req.into_response(Response::NotFound().finish()))
|
Ok(req.into_response(Response::NotFound().finish()))
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
|
||||||
// App config
|
// App config
|
||||||
let mut config = AppService::new(config, default.clone(), self.data.clone());
|
let mut config =
|
||||||
|
AppService::new(config, default.clone(), self.data_factories.clone());
|
||||||
|
|
||||||
// register services
|
// register services
|
||||||
std::mem::take(&mut *self.services.borrow_mut())
|
std::mem::take(&mut *self.services.borrow_mut())
|
||||||
|
@ -83,7 +83,7 @@ where
|
||||||
|
|
||||||
let (config, services) = config.into_services();
|
let (config, services) = config.into_services();
|
||||||
|
|
||||||
// complete pipeline creation
|
// complete pipeline creation.
|
||||||
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
|
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
|
||||||
default,
|
default,
|
||||||
services: services
|
services: services
|
||||||
|
@ -106,107 +106,46 @@ where
|
||||||
let rmap = Rc::new(rmap);
|
let rmap = Rc::new(rmap);
|
||||||
rmap.finish(rmap.clone());
|
rmap.finish(rmap.clone());
|
||||||
|
|
||||||
// start all data factory futures
|
// construct all async data factory futures
|
||||||
let factory_futs = join_all(self.data_factories.iter().map(|f| f()));
|
let factory_futs = join_all(self.async_data_factories.iter().map(|f| f()));
|
||||||
|
|
||||||
AppInitResult {
|
// construct app service and middleware service factory future.
|
||||||
endpoint: None,
|
let endpoint_fut = self.endpoint.new_service(());
|
||||||
endpoint_fut: self.endpoint.new_service(()),
|
|
||||||
data: self.data.clone(),
|
// take extensions or create new one as app data container.
|
||||||
data_factories: None,
|
let mut app_data = self
|
||||||
data_factories_fut: factory_futs.boxed_local(),
|
.extensions
|
||||||
extensions: Some(
|
|
||||||
self.extensions
|
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.take()
|
.take()
|
||||||
.unwrap_or_else(Extensions::new),
|
.unwrap_or_else(Extensions::new);
|
||||||
),
|
|
||||||
config,
|
|
||||||
rmap,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
let data_factories = self.data_factories.clone();
|
||||||
pub struct AppInitResult<T, B>
|
|
||||||
where
|
|
||||||
T: ServiceFactory<ServiceRequest>,
|
|
||||||
{
|
|
||||||
#[pin]
|
|
||||||
endpoint_fut: T::Future,
|
|
||||||
// a Some signals completion of endpoint creation
|
|
||||||
endpoint: Option<T::Service>,
|
|
||||||
|
|
||||||
#[pin]
|
|
||||||
data_factories_fut: LocalBoxFuture<'static, Vec<Result<Box<dyn DataFactory>, ()>>>,
|
|
||||||
// a Some signals completion of factory futures
|
|
||||||
data_factories: Option<Vec<Box<dyn DataFactory>>>,
|
|
||||||
|
|
||||||
rmap: Rc<ResourceMap>,
|
|
||||||
config: AppConfig,
|
|
||||||
data: Rc<[Box<dyn DataFactory>]>,
|
|
||||||
extensions: Option<Extensions>,
|
|
||||||
|
|
||||||
_phantom: PhantomData<B>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, B> Future for AppInitResult<T, B>
|
|
||||||
where
|
|
||||||
T: ServiceFactory<
|
|
||||||
ServiceRequest,
|
|
||||||
Config = (),
|
|
||||||
Response = ServiceResponse<B>,
|
|
||||||
Error = Error,
|
|
||||||
InitError = (),
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
type Output = Result<AppInitService<T::Service, B>, ()>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
// async data factories
|
// async data factories
|
||||||
if let Poll::Ready(factories) = this.data_factories_fut.poll(cx) {
|
let async_data_factories = factory_futs
|
||||||
let factories: Result<Vec<_>, ()> = factories.into_iter().collect();
|
.await
|
||||||
|
.into_iter()
|
||||||
if let Ok(factories) = factories {
|
.collect::<Result<Vec<_>, _>>()
|
||||||
this.data_factories.replace(factories);
|
.map_err(|_| ())?;
|
||||||
} else {
|
|
||||||
return Poll::Ready(Err(()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// app service and middleware
|
// app service and middleware
|
||||||
if this.endpoint.is_none() {
|
let service = endpoint_fut.await?;
|
||||||
if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? {
|
|
||||||
*this.endpoint = Some(srv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not using if let so condition only needs shared ref
|
// populate app data container from (async) data factories.
|
||||||
if this.endpoint.is_some() && this.data_factories.is_some() {
|
data_factories
|
||||||
// create app data container
|
.iter()
|
||||||
let mut data = this.extensions.take().unwrap();
|
.chain(&async_data_factories)
|
||||||
|
.for_each(|factory| {
|
||||||
|
factory.create(&mut app_data);
|
||||||
|
});
|
||||||
|
|
||||||
for f in this.data.iter() {
|
Ok(AppInitService {
|
||||||
f.create(&mut data);
|
service,
|
||||||
}
|
app_data: Rc::new(app_data),
|
||||||
|
app_state: AppInitServiceState::new(rmap, config),
|
||||||
for f in this.data_factories.take().unwrap().iter() {
|
})
|
||||||
f.create(&mut data);
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return Poll::Ready(Ok(AppInitService {
|
|
||||||
service: this.endpoint.take().unwrap(),
|
|
||||||
rmap: this.rmap.clone(),
|
|
||||||
config: this.config.clone(),
|
|
||||||
data: Rc::new(data),
|
|
||||||
pool: HttpRequestPool::create(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,10 +155,42 @@ where
|
||||||
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
{
|
{
|
||||||
service: T,
|
service: T,
|
||||||
|
app_data: Rc<Extensions>,
|
||||||
|
app_state: Rc<AppInitServiceState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// a collection of AppInitService state that shared between HttpRequests.
|
||||||
|
pub(crate) struct AppInitServiceState {
|
||||||
rmap: Rc<ResourceMap>,
|
rmap: Rc<ResourceMap>,
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
data: Rc<Extensions>,
|
pool: HttpRequestPool,
|
||||||
pool: &'static HttpRequestPool,
|
}
|
||||||
|
|
||||||
|
impl AppInitServiceState {
|
||||||
|
pub(crate) fn new(rmap: Rc<ResourceMap>, config: AppConfig) -> Rc<Self> {
|
||||||
|
Rc::new(AppInitServiceState {
|
||||||
|
rmap,
|
||||||
|
config,
|
||||||
|
// TODO: AppConfig can be used to pass user defined HttpRequestPool
|
||||||
|
// capacity.
|
||||||
|
pool: HttpRequestPool::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn rmap(&self) -> &ResourceMap {
|
||||||
|
&*self.rmap
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn config(&self) -> &AppConfig {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn pool(&self) -> &HttpRequestPool {
|
||||||
|
&self.pool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> Service<Request> for AppInitService<T, B>
|
impl<T, B> Service<Request> for AppInitService<T, B>
|
||||||
|
@ -230,32 +201,26 @@ where
|
||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
type Future = T::Future;
|
type Future = T::Future;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
let (head, payload) = req.into_parts();
|
let (head, payload) = req.into_parts();
|
||||||
|
|
||||||
let req = if let Some(mut req) = self.pool.get_request() {
|
let req = if let Some(mut req) = self.app_state.pool().pop() {
|
||||||
let inner = Rc::get_mut(&mut req.inner).unwrap();
|
let inner = Rc::get_mut(&mut req.inner).unwrap();
|
||||||
inner.path.get_mut().update(&head.uri);
|
inner.path.get_mut().update(&head.uri);
|
||||||
inner.path.reset();
|
inner.path.reset();
|
||||||
inner.head = head;
|
inner.head = head;
|
||||||
inner.payload = payload;
|
|
||||||
req
|
req
|
||||||
} else {
|
} else {
|
||||||
HttpRequest::new(
|
HttpRequest::new(
|
||||||
Path::new(Url::new(head.uri.clone())),
|
Path::new(Url::new(head.uri.clone())),
|
||||||
head,
|
head,
|
||||||
payload,
|
self.app_state.clone(),
|
||||||
self.rmap.clone(),
|
self.app_data.clone(),
|
||||||
self.config.clone(),
|
|
||||||
self.data.clone(),
|
|
||||||
self.pool,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.service.call(ServiceRequest::new(req))
|
self.service.call(ServiceRequest::new(req, payload))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +229,7 @@ where
|
||||||
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.pool.clear();
|
self.app_state.pool().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,127 +239,60 @@ pub struct AppRoutingFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
type Config = ();
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type InitError = ();
|
type Config = ();
|
||||||
type Service = AppRouting;
|
type Service = AppRouting;
|
||||||
type Future = AppRoutingFactoryResponse;
|
type InitError = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
AppRoutingFactoryResponse {
|
// construct all services factory future with it's resource def and guards.
|
||||||
fut: self
|
let factory_fut =
|
||||||
.services
|
join_all(self.services.iter().map(|(path, factory, guards)| {
|
||||||
.iter()
|
let path = path.clone();
|
||||||
.map(|(path, service, guards)| {
|
let guards = guards.borrow_mut().take();
|
||||||
CreateAppRoutingItem::Future(
|
let factory_fut = factory.new_service(());
|
||||||
Some(path.clone()),
|
async move {
|
||||||
guards.borrow_mut().take(),
|
let service = factory_fut.await?;
|
||||||
service.new_service(()).boxed_local(),
|
Ok((path, guards, service))
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
default: None,
|
|
||||||
default_fut: Some(self.default.new_service(())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
type HttpServiceFut = LocalBoxFuture<'static, Result<HttpService, ()>>;
|
// construct default service factory future
|
||||||
|
let default_fut = self.default.new_service(());
|
||||||
|
|
||||||
/// Create app service
|
Box::pin(async move {
|
||||||
#[doc(hidden)]
|
let default = default_fut.await?;
|
||||||
pub struct AppRoutingFactoryResponse {
|
|
||||||
fut: Vec<CreateAppRoutingItem>,
|
|
||||||
default: Option<HttpService>,
|
|
||||||
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CreateAppRoutingItem {
|
// build router from the factory future result.
|
||||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
|
let router = factory_fut
|
||||||
Service(ResourceDef, Option<Guards>, HttpService),
|
.await
|
||||||
}
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
impl Future for AppRoutingFactoryResponse {
|
|
||||||
type Output = Result<AppRouting, ()>;
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut done = true;
|
|
||||||
|
|
||||||
if let Some(ref mut fut) = self.default_fut {
|
|
||||||
match Pin::new(fut).poll(cx)? {
|
|
||||||
Poll::Ready(default) => self.default = Some(default),
|
|
||||||
Poll::Pending => done = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// poll http services
|
|
||||||
for item in &mut self.fut {
|
|
||||||
let res = match item {
|
|
||||||
CreateAppRoutingItem::Future(
|
|
||||||
ref mut path,
|
|
||||||
ref mut guards,
|
|
||||||
ref mut fut,
|
|
||||||
) => match Pin::new(fut).poll(cx) {
|
|
||||||
Poll::Ready(Ok(service)) => {
|
|
||||||
Some((path.take().unwrap(), guards.take(), service))
|
|
||||||
}
|
|
||||||
Poll::Ready(Err(_)) => return Poll::Ready(Err(())),
|
|
||||||
Poll::Pending => {
|
|
||||||
done = false;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CreateAppRoutingItem::Service(_, _, _) => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((path, guards, service)) = res {
|
|
||||||
*item = CreateAppRoutingItem::Service(path, guards, service);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
let router = self
|
|
||||||
.fut
|
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.fold(Router::build(), |mut router, item| {
|
.fold(Router::build(), |mut router, (path, guards, service)| {
|
||||||
match item {
|
|
||||||
CreateAppRoutingItem::Service(path, guards, service) => {
|
|
||||||
router.rdef(path, service).2 = guards;
|
router.rdef(path, service).2 = guards;
|
||||||
}
|
|
||||||
CreateAppRoutingItem::Future(_, _, _) => unreachable!(),
|
|
||||||
}
|
|
||||||
router
|
router
|
||||||
});
|
})
|
||||||
Poll::Ready(Ok(AppRouting {
|
.finish();
|
||||||
ready: None,
|
|
||||||
router: router.finish(),
|
Ok(AppRouting { router, default })
|
||||||
default: self.default.take(),
|
})
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppRouting {
|
pub struct AppRouting {
|
||||||
router: Router<HttpService, Guards>,
|
router: Router<HttpService, Guards>,
|
||||||
ready: Option<(ServiceRequest, ResourceInfo)>,
|
default: HttpService,
|
||||||
default: Option<HttpService>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for AppRouting {
|
impl Service<ServiceRequest> for AppRouting {
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = BoxResponse;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
if self.ready.is_none() {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
} else {
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
||||||
|
@ -410,11 +308,8 @@ impl Service<ServiceRequest> for AppRouting {
|
||||||
|
|
||||||
if let Some((srv, _info)) = res {
|
if let Some((srv, _info)) = res {
|
||||||
srv.call(req)
|
srv.call(req)
|
||||||
} else if let Some(ref mut default) = self.default {
|
|
||||||
default.call(req)
|
|
||||||
} else {
|
} else {
|
||||||
let req = req.into_parts().0;
|
self.default.call(req)
|
||||||
ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,12 +326,12 @@ impl AppEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for AppEntry {
|
impl ServiceFactory<ServiceRequest> for AppEntry {
|
||||||
type Config = ();
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type InitError = ();
|
type Config = ();
|
||||||
type Service = AppRouting;
|
type Service = AppRouting;
|
||||||
type Future = AppRoutingFactoryResponse;
|
type InitError = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
self.factory.borrow_mut().as_mut().unwrap().new_service(())
|
self.factory.borrow_mut().as_mut().unwrap().new_service(())
|
||||||
|
@ -448,9 +343,10 @@ mod tests {
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use actix_service::Service;
|
||||||
|
|
||||||
use crate::test::{init_service, TestRequest};
|
use crate::test::{init_service, TestRequest};
|
||||||
use crate::{web, App, HttpResponse};
|
use crate::{web, App, HttpResponse};
|
||||||
use actix_service::Service;
|
|
||||||
|
|
||||||
struct DropData(Arc<AtomicBool>);
|
struct DropData(Arc<AtomicBool>);
|
||||||
|
|
||||||
|
|
|
@ -125,9 +125,7 @@ impl AppService {
|
||||||
|
|
||||||
/// Application connection config
|
/// Application connection config
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppConfig(Rc<AppConfigInner>);
|
pub struct AppConfig {
|
||||||
|
|
||||||
struct AppConfigInner {
|
|
||||||
secure: bool,
|
secure: bool,
|
||||||
host: String,
|
host: String,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
|
@ -135,7 +133,7 @@ struct AppConfigInner {
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self {
|
pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self {
|
||||||
AppConfig(Rc::new(AppConfigInner { secure, addr, host }))
|
AppConfig { secure, addr, host }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server host name.
|
/// Server host name.
|
||||||
|
@ -146,17 +144,17 @@ impl AppConfig {
|
||||||
///
|
///
|
||||||
/// By default host name is set to a "localhost" value.
|
/// By default host name is set to a "localhost" value.
|
||||||
pub fn host(&self) -> &str {
|
pub fn host(&self) -> &str {
|
||||||
&self.0.host
|
&self.host
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if connection is secure(https)
|
/// Returns true if connection is secure(https)
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.0.secure
|
self.secure
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the socket address of the local half of this TCP connection
|
/// Returns the socket address of the local half of this TCP connection
|
||||||
pub fn local_addr(&self) -> SocketAddr {
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
self.0.addr
|
self.addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
src/error.rs
46
src/error.rs
|
@ -1,12 +1,11 @@
|
||||||
//! Error and Result module
|
//! Error and Result module
|
||||||
|
|
||||||
pub use actix_http::error::*;
|
pub use actix_http::error::*;
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, Error, From};
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
use url::ParseError as UrlParseError;
|
use url::ParseError as UrlParseError;
|
||||||
|
|
||||||
use crate::http::StatusCode;
|
use crate::{http::StatusCode, HttpResponse};
|
||||||
use crate::HttpResponse;
|
|
||||||
|
|
||||||
/// Errors which can occur when attempting to generate resource uri.
|
/// Errors which can occur when attempting to generate resource uri.
|
||||||
#[derive(Debug, PartialEq, Display, From)]
|
#[derive(Debug, PartialEq, Display, From)]
|
||||||
|
@ -28,34 +27,37 @@ impl std::error::Error for UrlGenerationError {}
|
||||||
impl ResponseError for UrlGenerationError {}
|
impl ResponseError for UrlGenerationError {}
|
||||||
|
|
||||||
/// A set of errors that can occur during parsing urlencoded payloads
|
/// A set of errors that can occur during parsing urlencoded payloads
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, Error, From)]
|
||||||
pub enum UrlencodedError {
|
pub enum UrlencodedError {
|
||||||
/// Can not decode chunked transfer encoding
|
/// Can not decode chunked transfer encoding.
|
||||||
#[display(fmt = "Can not decode chunked transfer encoding")]
|
#[display(fmt = "Can not decode chunked transfer encoding.")]
|
||||||
Chunked,
|
Chunked,
|
||||||
/// Payload size is bigger than allowed. (default: 256kB)
|
|
||||||
|
/// Payload size is larger than allowed. (default limit: 256kB).
|
||||||
#[display(
|
#[display(
|
||||||
fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)",
|
fmt = "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).",
|
||||||
size,
|
size,
|
||||||
limit
|
limit
|
||||||
)]
|
)]
|
||||||
Overflow { size: usize, limit: usize },
|
Overflow { size: usize, limit: usize },
|
||||||
/// Payload size is now known
|
|
||||||
#[display(fmt = "Payload size is now known")]
|
/// Payload size is now known.
|
||||||
|
#[display(fmt = "Payload size is now known.")]
|
||||||
UnknownLength,
|
UnknownLength,
|
||||||
/// Content type error
|
|
||||||
#[display(fmt = "Content type error")]
|
/// Content type error.
|
||||||
|
#[display(fmt = "Content type error.")]
|
||||||
ContentType,
|
ContentType,
|
||||||
/// Parse error
|
|
||||||
#[display(fmt = "Parse error")]
|
/// Parse error.
|
||||||
|
#[display(fmt = "Parse error.")]
|
||||||
Parse,
|
Parse,
|
||||||
/// Payload error
|
|
||||||
#[display(fmt = "Error that occur during reading payload: {}", _0)]
|
/// Payload error.
|
||||||
|
#[display(fmt = "Error that occur during reading payload: {}.", _0)]
|
||||||
Payload(PayloadError),
|
Payload(PayloadError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for UrlencodedError {}
|
|
||||||
|
|
||||||
/// Return `BadRequest` for `UrlencodedError`
|
/// Return `BadRequest` for `UrlencodedError`
|
||||||
impl ResponseError for UrlencodedError {
|
impl ResponseError for UrlencodedError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
|
@ -115,16 +117,14 @@ impl ResponseError for PathError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of errors that can occur during parsing query strings
|
/// A set of errors that can occur during parsing query strings.
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, Error, From)]
|
||||||
pub enum QueryPayloadError {
|
pub enum QueryPayloadError {
|
||||||
/// Deserialize error
|
/// Query deserialize error.
|
||||||
#[display(fmt = "Query deserialize error: {}", _0)]
|
#[display(fmt = "Query deserialize error: {}", _0)]
|
||||||
Deserialize(serde::de::value::Error),
|
Deserialize(serde::de::value::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for QueryPayloadError {}
|
|
||||||
|
|
||||||
/// Return `BadRequest` for `QueryPayloadError`
|
/// Return `BadRequest` for `QueryPayloadError`
|
||||||
impl ResponseError for QueryPayloadError {
|
impl ResponseError for QueryPayloadError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
|
|
|
@ -1,34 +1,37 @@
|
||||||
//! Request extractors
|
//! Request extractors
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::error::Error;
|
use std::{
|
||||||
use futures_util::future::{ready, Ready};
|
future::Future,
|
||||||
use futures_util::ready;
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::dev::Payload;
|
use futures_util::{
|
||||||
use crate::request::HttpRequest;
|
future::{ready, Ready},
|
||||||
|
ready,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{dev::Payload, Error, HttpRequest};
|
||||||
|
|
||||||
/// Trait implemented by types that can be extracted from request.
|
/// Trait implemented by types that can be extracted from request.
|
||||||
///
|
///
|
||||||
/// Types that implement this trait can be used with `Route` handlers.
|
/// Types that implement this trait can be used with `Route` handlers.
|
||||||
pub trait FromRequest: Sized {
|
pub trait FromRequest: Sized {
|
||||||
|
/// Configuration for this extractor.
|
||||||
|
type Config: Default + 'static;
|
||||||
|
|
||||||
/// The associated error which can be returned.
|
/// The associated error which can be returned.
|
||||||
type Error: Into<Error>;
|
type Error: Into<Error>;
|
||||||
|
|
||||||
/// Future that resolves to a Self
|
/// Future that resolves to a Self.
|
||||||
type Future: Future<Output = Result<Self, Self::Error>>;
|
type Future: Future<Output = Result<Self, Self::Error>>;
|
||||||
|
|
||||||
/// Configuration for this extractor
|
/// Create a Self from request parts asynchronously.
|
||||||
type Config: Default + 'static;
|
|
||||||
|
|
||||||
/// Convert request to a Self
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
|
||||||
|
|
||||||
/// Convert request to a Self
|
/// Create a Self from request head asynchronously.
|
||||||
///
|
///
|
||||||
/// This method uses `Payload::None` as payload stream.
|
/// This method is short for `T::from_request(req, &mut Payload::None)`.
|
||||||
fn extract(req: &HttpRequest) -> Self::Future {
|
fn extract(req: &HttpRequest) -> Self::Future {
|
||||||
Self::from_request(req, &mut Payload::None)
|
Self::from_request(req, &mut Payload::None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,6 @@ where
|
||||||
{
|
{
|
||||||
Extract(#[pin] T::Future, Option<HttpRequest>, F),
|
Extract(#[pin] T::Future, Option<HttpRequest>, F),
|
||||||
Handle(#[pin] R, Option<HttpRequest>),
|
Handle(#[pin] R, Option<HttpRequest>),
|
||||||
Respond(#[pin] <R::Output as Responder>::Future, Option<HttpRequest>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T, R> Future for HandlerServiceFuture<F, T, R>
|
impl<F, T, R> Future for HandlerServiceFuture<F, T, R>
|
||||||
|
@ -168,13 +167,8 @@ where
|
||||||
}
|
}
|
||||||
HandlerProj::Handle(fut, req) => {
|
HandlerProj::Handle(fut, req) => {
|
||||||
let res = ready!(fut.poll(cx));
|
let res = ready!(fut.poll(cx));
|
||||||
let fut = res.respond_to(req.as_ref().unwrap());
|
|
||||||
let state = HandlerServiceFuture::Respond(fut, req.take());
|
|
||||||
self.as_mut().set(state);
|
|
||||||
}
|
|
||||||
HandlerProj::Respond(fut, req) => {
|
|
||||||
let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into());
|
|
||||||
let req = req.take().unwrap();
|
let req = req.take().unwrap();
|
||||||
|
let res = res.respond_to(&req);
|
||||||
return Poll::Ready(Ok(ServiceResponse::new(req, res)));
|
return Poll::Ready(Ok(ServiceResponse::new(req, res)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
//! use actix_web::{get, web, App, HttpServer, Responder};
|
//! use actix_web::{get, web, App, HttpServer, Responder};
|
||||||
//!
|
//!
|
||||||
//! #[get("/{id}/{name}/index.html")]
|
//! #[get("/{id}/{name}/index.html")]
|
||||||
//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
|
//! async fn index(path: web::Path<(u32, String)>) -> impl Responder {
|
||||||
|
//! let (id, name) = path.into_inner();
|
||||||
//! format!("Hello {}! id:{}", name, id)
|
//! format!("Hello {}! id:{}", name, id)
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
|
@ -90,7 +91,7 @@ mod scope;
|
||||||
mod server;
|
mod server;
|
||||||
mod service;
|
mod service;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
mod types;
|
pub(crate) mod types;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
pub use actix_http::Response as HttpResponse;
|
pub use actix_http::Response as HttpResponse;
|
||||||
|
@ -106,6 +107,7 @@ pub use crate::responder::Responder;
|
||||||
pub use crate::route::Route;
|
pub use crate::route::Route;
|
||||||
pub use crate::scope::Scope;
|
pub use crate::scope::Scope;
|
||||||
pub use crate::server::HttpServer;
|
pub use crate::server::HttpServer;
|
||||||
|
// TODO: is exposing the error directly really needed
|
||||||
pub use crate::types::{Either, EitherExtractError};
|
pub use crate::types::{Either, EitherExtractError};
|
||||||
|
|
||||||
pub mod dev {
|
pub mod dev {
|
||||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use log::debug;
|
use log::{debug, warn};
|
||||||
use regex::{Regex, RegexSet};
|
use regex::{Regex, RegexSet};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
@ -188,9 +188,8 @@ where
|
||||||
for unit in &self.0.format.0 {
|
for unit in &self.0.format.0 {
|
||||||
// missing request replacement function diagnostic
|
// missing request replacement function diagnostic
|
||||||
if let FormatText::CustomRequest(label, None) = unit {
|
if let FormatText::CustomRequest(label, None) = unit {
|
||||||
debug!(
|
warn!(
|
||||||
"No custom request replacement function was registered for label {} in\
|
"No custom request replacement function was registered for label \"{}\".",
|
||||||
logger format.",
|
|
||||||
label
|
label
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
110
src/request.rs
110
src/request.rs
|
@ -8,6 +8,7 @@ use actix_router::{Path, Url};
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::app_service::AppInitServiceState;
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::error::UrlGenerationError;
|
use crate::error::UrlGenerationError;
|
||||||
use crate::extract::FromRequest;
|
use crate::extract::FromRequest;
|
||||||
|
@ -17,20 +18,18 @@ use crate::rmap::ResourceMap;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
/// An HTTP Request
|
/// An HTTP Request
|
||||||
pub struct HttpRequest {
|
pub struct HttpRequest {
|
||||||
// *. Rc<HttpRequestInner> is used exclusively and NO Weak<HttpRequestInner>
|
/// # Panics
|
||||||
// is allowed anywhere in the code. Weak pointer is purposely ignored when
|
/// `Rc<HttpRequestInner>` is used exclusively and NO `Weak<HttpRequestInner>`
|
||||||
// doing Rc's ref counter check.
|
/// is allowed anywhere in the code. Weak pointer is purposely ignored when
|
||||||
|
/// doing `Rc`'s ref counter check. Expect panics if this invariant is violated.
|
||||||
pub(crate) inner: Rc<HttpRequestInner>,
|
pub(crate) inner: Rc<HttpRequestInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct HttpRequestInner {
|
pub(crate) struct HttpRequestInner {
|
||||||
pub(crate) head: Message<RequestHead>,
|
pub(crate) head: Message<RequestHead>,
|
||||||
pub(crate) path: Path<Url>,
|
pub(crate) path: Path<Url>,
|
||||||
pub(crate) payload: Payload,
|
|
||||||
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
|
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
|
||||||
rmap: Rc<ResourceMap>,
|
app_state: Rc<AppInitServiceState>,
|
||||||
config: AppConfig,
|
|
||||||
pool: &'static HttpRequestPool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpRequest {
|
impl HttpRequest {
|
||||||
|
@ -38,11 +37,8 @@ impl HttpRequest {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
path: Path<Url>,
|
path: Path<Url>,
|
||||||
head: Message<RequestHead>,
|
head: Message<RequestHead>,
|
||||||
payload: Payload,
|
app_state: Rc<AppInitServiceState>,
|
||||||
rmap: Rc<ResourceMap>,
|
|
||||||
config: AppConfig,
|
|
||||||
app_data: Rc<Extensions>,
|
app_data: Rc<Extensions>,
|
||||||
pool: &'static HttpRequestPool,
|
|
||||||
) -> HttpRequest {
|
) -> HttpRequest {
|
||||||
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
|
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
|
||||||
data.push(app_data);
|
data.push(app_data);
|
||||||
|
@ -51,11 +47,8 @@ impl HttpRequest {
|
||||||
inner: Rc::new(HttpRequestInner {
|
inner: Rc::new(HttpRequestInner {
|
||||||
head,
|
head,
|
||||||
path,
|
path,
|
||||||
payload,
|
app_state,
|
||||||
rmap,
|
|
||||||
config,
|
|
||||||
app_data: data,
|
app_data: data,
|
||||||
pool,
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +134,7 @@ impl HttpRequest {
|
||||||
/// Returns a None when no resource is fully matched, including default services.
|
/// Returns a None when no resource is fully matched, including default services.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_pattern(&self) -> Option<String> {
|
pub fn match_pattern(&self) -> Option<String> {
|
||||||
self.inner.rmap.match_pattern(self.path())
|
self.resource_map().match_pattern(self.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The resource name that matched the path. Useful for logging and metrics.
|
/// The resource name that matched the path. Useful for logging and metrics.
|
||||||
|
@ -149,7 +142,7 @@ impl HttpRequest {
|
||||||
/// Returns a None when no resource is fully matched, including default services.
|
/// Returns a None when no resource is fully matched, including default services.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_name(&self) -> Option<&str> {
|
pub fn match_name(&self) -> Option<&str> {
|
||||||
self.inner.rmap.match_name(self.path())
|
self.resource_map().match_name(self.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request extensions
|
/// Request extensions
|
||||||
|
@ -191,7 +184,7 @@ impl HttpRequest {
|
||||||
U: IntoIterator<Item = I>,
|
U: IntoIterator<Item = I>,
|
||||||
I: AsRef<str>,
|
I: AsRef<str>,
|
||||||
{
|
{
|
||||||
self.inner.rmap.url_for(&self, name, elements)
|
self.resource_map().url_for(&self, name, elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate url for named resource
|
/// Generate url for named resource
|
||||||
|
@ -206,7 +199,7 @@ impl HttpRequest {
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Get a reference to a `ResourceMap` of current application.
|
/// Get a reference to a `ResourceMap` of current application.
|
||||||
pub fn resource_map(&self) -> &ResourceMap {
|
pub fn resource_map(&self) -> &ResourceMap {
|
||||||
&self.inner.rmap
|
&self.app_state().rmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer socket address
|
/// Peer socket address
|
||||||
|
@ -226,13 +219,13 @@ impl HttpRequest {
|
||||||
/// borrowed.
|
/// borrowed.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||||
ConnectionInfo::get(self.head(), &*self.app_config())
|
ConnectionInfo::get(self.head(), self.app_config())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// App config
|
/// App config
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn app_config(&self) -> &AppConfig {
|
pub fn app_config(&self) -> &AppConfig {
|
||||||
&self.inner.config
|
self.app_state().config()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an application data object stored with `App::data` or `App::app_data`
|
/// Get an application data object stored with `App::data` or `App::app_data`
|
||||||
|
@ -252,6 +245,11 @@ impl HttpRequest {
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn app_state(&self) -> &AppInitServiceState {
|
||||||
|
&*self.inner.app_state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpMessage for HttpRequest {
|
impl HttpMessage for HttpRequest {
|
||||||
|
@ -287,14 +285,16 @@ impl Drop for HttpRequest {
|
||||||
|
|
||||||
// This relies on no Weak<HttpRequestInner> exists anywhere.(There is none)
|
// This relies on no Weak<HttpRequestInner> exists anywhere.(There is none)
|
||||||
if let Some(inner) = Rc::get_mut(&mut self.inner) {
|
if let Some(inner) = Rc::get_mut(&mut self.inner) {
|
||||||
let v = &mut inner.pool.0.borrow_mut();
|
if inner.app_state.pool().is_available() {
|
||||||
if v.len() < 128 {
|
|
||||||
// clear additional app_data and keep the root one for reuse.
|
// clear additional app_data and keep the root one for reuse.
|
||||||
inner.app_data.truncate(1);
|
inner.app_data.truncate(1);
|
||||||
// inner is borrowed mut here. get head's Extension mutably
|
// inner is borrowed mut here. get head's Extension mutably
|
||||||
// to reduce borrow check
|
// to reduce borrow check
|
||||||
inner.head.extensions.get_mut().clear();
|
inner.head.extensions.get_mut().clear();
|
||||||
v.push(self.inner.clone());
|
|
||||||
|
// a re-borrow of pool is necessary here.
|
||||||
|
let req = self.inner.clone();
|
||||||
|
self.app_state().pool().push(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,25 +363,50 @@ impl fmt::Debug for HttpRequest {
|
||||||
/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
|
/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
|
||||||
/// in `<AppInitService as Service>::call` when there are available objects in the list.
|
/// in `<AppInitService as Service>::call` when there are available objects in the list.
|
||||||
///
|
///
|
||||||
/// The pool's initial capacity is 128 items.
|
/// The pool's default capacity is 128 items.
|
||||||
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
|
pub(crate) struct HttpRequestPool {
|
||||||
|
inner: RefCell<Vec<Rc<HttpRequestInner>>>,
|
||||||
|
cap: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpRequestPool {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::with_capacity(128)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HttpRequestPool {
|
impl HttpRequestPool {
|
||||||
/// Allocates a slab of memory for pool use.
|
pub(crate) fn with_capacity(cap: usize) -> Self {
|
||||||
pub(crate) fn create() -> &'static HttpRequestPool {
|
HttpRequestPool {
|
||||||
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
|
inner: RefCell::new(Vec::with_capacity(cap)),
|
||||||
Box::leak(Box::new(pool))
|
cap,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
|
/// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_request(&self) -> Option<HttpRequest> {
|
pub(crate) fn pop(&self) -> Option<HttpRequest> {
|
||||||
self.0.borrow_mut().pop().map(|inner| HttpRequest { inner })
|
self.inner
|
||||||
|
.borrow_mut()
|
||||||
|
.pop()
|
||||||
|
.map(|inner| HttpRequest { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the pool still has capacity for request storage.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn is_available(&self) -> bool {
|
||||||
|
self.inner.borrow_mut().len() < self.cap
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a request to pool.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push(&self, req: Rc<HttpRequestInner>) {
|
||||||
|
self.inner.borrow_mut().push(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all allocated HttpRequest objects.
|
/// Clears all allocated HttpRequest objects.
|
||||||
pub(crate) fn clear(&self) {
|
pub(crate) fn clear(&self) {
|
||||||
self.0.borrow_mut().clear()
|
self.inner.borrow_mut().clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,6 +552,25 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_drop_http_request_pool() {
|
||||||
|
let mut srv = init_service(App::new().service(web::resource("/").to(
|
||||||
|
|req: HttpRequest| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.set_header("pool_cap", req.app_state().pool().cap)
|
||||||
|
.finish()
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_request();
|
||||||
|
let resp = call_service(&mut srv, req).await;
|
||||||
|
|
||||||
|
drop(srv);
|
||||||
|
|
||||||
|
assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_data() {
|
async fn test_data() {
|
||||||
let mut srv = init_service(App::new().app_data(10usize).service(
|
let mut srv = init_service(App::new().app_data(10usize).service(
|
||||||
|
|
170
src/resource.rs
170
src/resource.rs
|
@ -1,18 +1,18 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::Poll;
|
||||||
|
|
||||||
use actix_http::{Error, Extensions, Response};
|
use actix_http::{Error, Extensions, Response};
|
||||||
use actix_router::IntoPattern;
|
use actix_router::IntoPattern;
|
||||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory,
|
apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||||
ServiceFactoryExt, Transform,
|
ServiceFactoryExt, Transform,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
|
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
|
||||||
|
@ -20,7 +20,7 @@ use crate::extract::FromRequest;
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
use crate::handler::Handler;
|
use crate::handler::Handler;
|
||||||
use crate::responder::Responder;
|
use crate::responder::Responder;
|
||||||
use crate::route::{CreateRouteService, Route, RouteService};
|
use crate::route::{Route, RouteService};
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
use crate::service::{ServiceRequest, ServiceResponse};
|
||||||
|
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||||
|
@ -53,9 +53,9 @@ pub struct Resource<T = ResourceEndpoint> {
|
||||||
rdef: Vec<String>,
|
rdef: Vec<String>,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
data: Option<Extensions>,
|
app_data: Option<Extensions>,
|
||||||
guards: Vec<Box<dyn Guard>>,
|
guards: Vec<Box<dyn Guard>>,
|
||||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
default: HttpNewService,
|
||||||
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +70,10 @@ impl Resource {
|
||||||
endpoint: ResourceEndpoint::new(fref.clone()),
|
endpoint: ResourceEndpoint::new(fref.clone()),
|
||||||
factory_ref: fref,
|
factory_ref: fref,
|
||||||
guards: Vec::new(),
|
guards: Vec::new(),
|
||||||
data: None,
|
app_data: None,
|
||||||
default: Rc::new(RefCell::new(None)),
|
default: boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||||
|
Ok(req.into_response(Response::MethodNotAllowed().finish()))
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,10 +203,10 @@ where
|
||||||
///
|
///
|
||||||
/// Data of different types from parent contexts will still be accessible.
|
/// Data of different types from parent contexts will still be accessible.
|
||||||
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
if self.data.is_none() {
|
if self.app_data.is_none() {
|
||||||
self.data = Some(Extensions::new());
|
self.app_data = Some(Extensions::new());
|
||||||
}
|
}
|
||||||
self.data.as_mut().unwrap().insert(data);
|
self.app_data.as_mut().unwrap().insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +276,7 @@ where
|
||||||
guards: self.guards,
|
guards: self.guards,
|
||||||
routes: self.routes,
|
routes: self.routes,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
data: self.data,
|
app_data: self.app_data,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,7 +338,7 @@ where
|
||||||
guards: self.guards,
|
guards: self.guards,
|
||||||
routes: self.routes,
|
routes: self.routes,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
data: self.data,
|
app_data: self.app_data,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,11 +358,9 @@ where
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
// create and configure default resource
|
// create and configure default resource
|
||||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
self.default = boxed::factory(f.into_factory().map_init_err(|e| {
|
||||||
f.into_factory().map_init_err(|e| {
|
|
||||||
log::error!("Can not construct default service: {:?}", e)
|
log::error!("Can not construct default service: {:?}", e)
|
||||||
}),
|
}));
|
||||||
)))));
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -391,7 +391,7 @@ where
|
||||||
*rdef.name_mut() = name.clone();
|
*rdef.name_mut() = name.clone();
|
||||||
}
|
}
|
||||||
// custom app data storage
|
// custom app data storage
|
||||||
if let Some(ref mut ext) = self.data {
|
if let Some(ref mut ext) = self.app_data {
|
||||||
config.set_service_data(ext);
|
config.set_service_data(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ where
|
||||||
fn into_factory(self) -> T {
|
fn into_factory(self) -> T {
|
||||||
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
||||||
routes: self.routes,
|
routes: self.routes,
|
||||||
data: self.data.map(Rc::new),
|
app_data: self.app_data.map(Rc::new),
|
||||||
default: self.default,
|
default: self.default,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -422,8 +422,8 @@ where
|
||||||
|
|
||||||
pub struct ResourceFactory {
|
pub struct ResourceFactory {
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
data: Option<Rc<Extensions>>,
|
app_data: Option<Rc<Extensions>>,
|
||||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
default: HttpNewService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||||
|
@ -432,126 +432,60 @@ impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||||
type Config = ();
|
type Config = ();
|
||||||
type Service = ResourceService;
|
type Service = ResourceService;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Future = CreateResourceService;
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let default_fut = if let Some(ref default) = *self.default.borrow() {
|
// construct default service factory future.
|
||||||
Some(default.new_service(()))
|
let default_fut = self.default.new_service(());
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
CreateResourceService {
|
// construct route service factory futures
|
||||||
fut: self
|
let factory_fut =
|
||||||
.routes
|
join_all(self.routes.iter().map(|route| route.new_service(())));
|
||||||
.iter()
|
|
||||||
.map(|route| CreateRouteServiceItem::Future(route.new_service(())))
|
|
||||||
.collect(),
|
|
||||||
data: self.data.clone(),
|
|
||||||
default: None,
|
|
||||||
default_fut,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CreateRouteServiceItem {
|
let app_data = self.app_data.clone();
|
||||||
Future(CreateRouteService),
|
|
||||||
Service(RouteService),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CreateResourceService {
|
Box::pin(async move {
|
||||||
fut: Vec<CreateRouteServiceItem>,
|
let default = default_fut.await?;
|
||||||
data: Option<Rc<Extensions>>,
|
let routes = factory_fut
|
||||||
default: Option<HttpService>,
|
.await
|
||||||
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
|
.into_iter()
|
||||||
}
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
impl Future for CreateResourceService {
|
Ok(ResourceService {
|
||||||
type Output = Result<ResourceService, ()>;
|
app_data,
|
||||||
|
default,
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut done = true;
|
|
||||||
|
|
||||||
if let Some(ref mut fut) = self.default_fut {
|
|
||||||
match Pin::new(fut).poll(cx)? {
|
|
||||||
Poll::Ready(default) => self.default = Some(default),
|
|
||||||
Poll::Pending => done = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// poll http services
|
|
||||||
for item in &mut self.fut {
|
|
||||||
match item {
|
|
||||||
CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut)
|
|
||||||
.poll(cx)?
|
|
||||||
{
|
|
||||||
Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route),
|
|
||||||
Poll::Pending => {
|
|
||||||
done = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CreateRouteServiceItem::Service(_) => continue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
let routes = self
|
|
||||||
.fut
|
|
||||||
.drain(..)
|
|
||||||
.map(|item| match item {
|
|
||||||
CreateRouteServiceItem::Service(service) => service,
|
|
||||||
CreateRouteServiceItem::Future(_) => unreachable!(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Poll::Ready(Ok(ResourceService {
|
|
||||||
routes,
|
routes,
|
||||||
data: self.data.clone(),
|
})
|
||||||
default: self.default.take(),
|
})
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ResourceService {
|
pub struct ResourceService {
|
||||||
routes: Vec<RouteService>,
|
routes: Vec<RouteService>,
|
||||||
data: Option<Rc<Extensions>>,
|
app_data: Option<Rc<Extensions>>,
|
||||||
default: Option<HttpService>,
|
default: HttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for ResourceService {
|
impl Service<ServiceRequest> for ResourceService {
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||||
for route in self.routes.iter_mut() {
|
for route in self.routes.iter_mut() {
|
||||||
if route.check(&mut req) {
|
if route.check(&mut req) {
|
||||||
if let Some(ref data) = self.data {
|
if let Some(ref app_data) = self.app_data {
|
||||||
req.add_data_container(data.clone());
|
req.add_data_container(app_data.clone());
|
||||||
}
|
}
|
||||||
return route.call(req);
|
return route.call(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ref mut default) = self.default {
|
if let Some(ref app_data) = self.app_data {
|
||||||
if let Some(ref data) = self.data {
|
req.add_data_container(app_data.clone());
|
||||||
req.add_data_container(data.clone());
|
|
||||||
}
|
|
||||||
default.call(req)
|
|
||||||
} else {
|
|
||||||
let req = req.into_parts().0;
|
|
||||||
Box::pin(async {
|
|
||||||
Ok(ServiceResponse::new(
|
|
||||||
req,
|
|
||||||
Response::MethodNotAllowed().finish(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
self.default.call(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,15 +501,15 @@ impl ResourceEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for ResourceEndpoint {
|
impl ServiceFactory<ServiceRequest> for ResourceEndpoint {
|
||||||
type Config = ();
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type InitError = ();
|
type Config = ();
|
||||||
type Service = ResourceService;
|
type Service = ResourceService;
|
||||||
type Future = CreateResourceService;
|
type InitError = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
self.factory.borrow_mut().as_mut().unwrap().new_service(())
|
self.factory.borrow().as_ref().unwrap().new_service(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
278
src/responder.rs
278
src/responder.rs
|
@ -1,33 +1,20 @@
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::error::InternalError;
|
use actix_http::error::InternalError;
|
||||||
use actix_http::http::{
|
use actix_http::http::{
|
||||||
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
|
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
|
||||||
};
|
};
|
||||||
use actix_http::{Error, Response, ResponseBuilder};
|
use actix_http::ResponseBuilder;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_util::future::{err, ok, Either as EitherFuture, Ready};
|
|
||||||
use futures_util::ready;
|
|
||||||
use pin_project::pin_project;
|
|
||||||
|
|
||||||
use crate::request::HttpRequest;
|
use crate::{Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
/// Trait implemented by types that can be converted to a http response.
|
/// Trait implemented by types that can be converted to a http response.
|
||||||
///
|
///
|
||||||
/// Types that implement this trait can be used as the return type of a handler.
|
/// Types that implement this trait can be used as the return type of a handler.
|
||||||
pub trait Responder {
|
pub trait Responder {
|
||||||
/// The associated error which can be returned.
|
/// Convert self to `HttpResponse`.
|
||||||
type Error: Into<Error>;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
|
||||||
|
|
||||||
/// The future response value.
|
|
||||||
type Future: Future<Output = Result<Response, Self::Error>>;
|
|
||||||
|
|
||||||
/// Convert itself to `AsyncResult` or `Error`.
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future;
|
|
||||||
|
|
||||||
/// Override a status code for a Responder.
|
/// Override a status code for a Responder.
|
||||||
///
|
///
|
||||||
|
@ -76,29 +63,18 @@ pub trait Responder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for Response {
|
impl Responder for HttpResponse {
|
||||||
type Error = Error;
|
|
||||||
type Future = Ready<Result<Response, Error>>;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
ok(self)
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for Option<T>
|
impl<T: Responder> Responder for Option<T> {
|
||||||
where
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
T: Responder,
|
|
||||||
{
|
|
||||||
type Error = T::Error;
|
|
||||||
type Future = EitherFuture<T::Future, Ready<Result<Response, T::Error>>>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
match self {
|
match self {
|
||||||
Some(t) => EitherFuture::Left(t.respond_to(req)),
|
Some(t) => t.respond_to(req),
|
||||||
None => {
|
None => HttpResponse::build(StatusCode::NOT_FOUND).finish(),
|
||||||
EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,109 +84,74 @@ where
|
||||||
T: Responder,
|
T: Responder,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
type Future = EitherFuture<
|
|
||||||
ResponseFuture<T::Future, T::Error>,
|
|
||||||
Ready<Result<Response, Error>>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
match self {
|
match self {
|
||||||
Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))),
|
Ok(val) => val.respond_to(req),
|
||||||
Err(e) => EitherFuture::Right(err(e.into())),
|
Err(e) => HttpResponse::from_error(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for ResponseBuilder {
|
impl Responder for ResponseBuilder {
|
||||||
type Error = Error;
|
|
||||||
type Future = Ready<Result<Response, Error>>;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(mut self, _: &HttpRequest) -> Self::Future {
|
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
||||||
ok(self.finish())
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for (T, StatusCode)
|
impl<T: Responder> Responder for (T, StatusCode) {
|
||||||
where
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
T: Responder,
|
let mut res = self.0.respond_to(req);
|
||||||
{
|
*res.status_mut() = self.1;
|
||||||
type Error = T::Error;
|
res
|
||||||
type Future = CustomResponderFut<T>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
CustomResponderFut {
|
|
||||||
fut: self.0.respond_to(req),
|
|
||||||
status: Some(self.1),
|
|
||||||
headers: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for &'static str {
|
impl Responder for &'static str {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("text/plain; charset=utf-8")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for &'static [u8] {
|
impl Responder for &'static [u8] {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("application/octet-stream")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for String {
|
impl Responder for String {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("text/plain; charset=utf-8")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Responder for &'a String {
|
impl<'a> Responder for &'a String {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("text/plain; charset=utf-8")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for Bytes {
|
impl Responder for Bytes {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("application/octet-stream")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for BytesMut {
|
impl Responder for BytesMut {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::Ok()
|
||||||
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.body(self)
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("application/octet-stream")
|
|
||||||
.body(self))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,45 +231,20 @@ impl<T: Responder> CustomResponder<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for CustomResponder<T> {
|
impl<T: Responder> Responder for CustomResponder<T> {
|
||||||
type Error = T::Error;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
type Future = CustomResponderFut<T>;
|
let mut res = self.responder.respond_to(req);
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
if let Some(status) = self.status {
|
||||||
CustomResponderFut {
|
|
||||||
fut: self.responder.respond_to(req),
|
|
||||||
status: self.status,
|
|
||||||
headers: self.headers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pin_project]
|
|
||||||
pub struct CustomResponderFut<T: Responder> {
|
|
||||||
#[pin]
|
|
||||||
fut: T::Future,
|
|
||||||
status: Option<StatusCode>,
|
|
||||||
headers: Option<HeaderMap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Responder> Future for CustomResponderFut<T> {
|
|
||||||
type Output = Result<Response, T::Error>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
|
|
||||||
let mut res = match ready!(this.fut.poll(cx)) {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(e) => return Poll::Ready(Err(e)),
|
|
||||||
};
|
|
||||||
if let Some(status) = this.status.take() {
|
|
||||||
*res.status_mut() = status;
|
*res.status_mut() = status;
|
||||||
}
|
}
|
||||||
if let Some(ref headers) = this.headers {
|
|
||||||
|
if let Some(ref headers) = self.headers {
|
||||||
for (k, v) in headers {
|
for (k, v) in headers {
|
||||||
res.headers_mut().insert(k.clone(), v.clone());
|
res.headers_mut().insert(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(Ok(res))
|
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,40 +252,8 @@ impl<T> Responder for InternalError<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + std::fmt::Display + 'static,
|
T: std::fmt::Debug + std::fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
HttpResponse::from_error(self.into())
|
||||||
|
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
|
||||||
let err: Error = self.into();
|
|
||||||
ok(err.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pin_project]
|
|
||||||
pub struct ResponseFuture<T, E> {
|
|
||||||
#[pin]
|
|
||||||
fut: T,
|
|
||||||
_phantom: PhantomData<E>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> ResponseFuture<T, E> {
|
|
||||||
pub fn new(fut: T) -> Self {
|
|
||||||
ResponseFuture {
|
|
||||||
fut,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> Future for ResponseFuture<T, E>
|
|
||||||
where
|
|
||||||
T: Future<Output = Result<Response, E>>,
|
|
||||||
E: Into<Error>,
|
|
||||||
{
|
|
||||||
type Output = Result<Response, Error>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +266,7 @@ pub(crate) mod tests {
|
||||||
use crate::dev::{Body, ResponseBody};
|
use crate::dev::{Body, ResponseBody};
|
||||||
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
||||||
use crate::test::{init_service, TestRequest};
|
use crate::test::{init_service, TestRequest};
|
||||||
use crate::{error, web, App, HttpResponse};
|
use crate::{error, web, App};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_option_responder() {
|
async fn test_option_responder() {
|
||||||
|
@ -441,7 +325,7 @@ pub(crate) mod tests {
|
||||||
async fn test_responder() {
|
async fn test_responder() {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
let resp: HttpResponse = "test".respond_to(&req).await.unwrap();
|
let resp = "test".respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -449,7 +333,7 @@ pub(crate) mod tests {
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp: HttpResponse = b"test".respond_to(&req).await.unwrap();
|
let resp = b"test".respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -457,7 +341,7 @@ pub(crate) mod tests {
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap();
|
let resp = "test".to_string().respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -465,7 +349,7 @@ pub(crate) mod tests {
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap();
|
let resp = (&"test".to_string()).respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -473,8 +357,7 @@ pub(crate) mod tests {
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp: HttpResponse =
|
let resp = Bytes::from_static(b"test").respond_to(&req);
|
||||||
Bytes::from_static(b"test").respond_to(&req).await.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -482,10 +365,7 @@ pub(crate) mod tests {
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp: HttpResponse = BytesMut::from(b"test".as_ref())
|
let resp = BytesMut::from(b"test".as_ref()).respond_to(&req);
|
||||||
.respond_to(&req)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -494,11 +374,8 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// InternalError
|
// InternalError
|
||||||
let resp: HttpResponse =
|
let resp =
|
||||||
error::InternalError::new("err", StatusCode::BAD_REQUEST)
|
error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
|
||||||
.respond_to(&req)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,10 +384,7 @@ pub(crate) mod tests {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
// Result<I, E>
|
// Result<I, E>
|
||||||
let resp: HttpResponse = Ok::<_, Error>("test".to_string())
|
let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
|
||||||
.respond_to(&req)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
assert_eq!(resp.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -520,9 +394,9 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
|
Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
|
||||||
.respond_to(&req)
|
.respond_to(&req);
|
||||||
.await;
|
|
||||||
assert!(res.is_err());
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -531,18 +405,15 @@ pub(crate) mod tests {
|
||||||
let res = "test"
|
let res = "test"
|
||||||
.to_string()
|
.to_string()
|
||||||
.with_status(StatusCode::BAD_REQUEST)
|
.with_status(StatusCode::BAD_REQUEST)
|
||||||
.respond_to(&req)
|
.respond_to(&req);
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(res.body().bin_ref(), b"test");
|
||||||
|
|
||||||
let res = "test"
|
let res = "test"
|
||||||
.to_string()
|
.to_string()
|
||||||
.with_header("content-type", "json")
|
.with_header("content-type", "json")
|
||||||
.respond_to(&req)
|
.respond_to(&req);
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(res.body().bin_ref(), b"test");
|
||||||
|
@ -555,19 +426,14 @@ pub(crate) mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_tuple_responder_with_status_code() {
|
async fn test_tuple_responder_with_status_code() {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = ("test".to_string(), StatusCode::BAD_REQUEST)
|
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
|
||||||
.respond_to(&req)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(res.body().bin_ref(), b"test");
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = ("test".to_string(), StatusCode::OK)
|
let res = ("test".to_string(), StatusCode::OK)
|
||||||
.with_header("content-type", "json")
|
.with_header("content-type", "json")
|
||||||
.respond_to(&req)
|
.respond_to(&req);
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(res.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
211
src/scope.rs
211
src/scope.rs
|
@ -1,18 +1,18 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::Poll;
|
||||||
|
|
||||||
use actix_http::{Extensions, Response};
|
use actix_http::Extensions;
|
||||||
use actix_router::{ResourceDef, ResourceInfo, Router};
|
use actix_router::{ResourceDef, Router};
|
||||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory,
|
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory,
|
||||||
ServiceFactoryExt, Transform,
|
ServiceFactoryExt, Transform,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
|
@ -61,10 +61,10 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err
|
||||||
pub struct Scope<T = ScopeEndpoint> {
|
pub struct Scope<T = ScopeEndpoint> {
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
rdef: String,
|
rdef: String,
|
||||||
data: Option<Extensions>,
|
app_data: Option<Extensions>,
|
||||||
services: Vec<Box<dyn AppServiceFactory>>,
|
services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
guards: Vec<Box<dyn Guard>>,
|
guards: Vec<Box<dyn Guard>>,
|
||||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
default: Option<Rc<HttpNewService>>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,10 @@ impl Scope {
|
||||||
Scope {
|
Scope {
|
||||||
endpoint: ScopeEndpoint::new(fref.clone()),
|
endpoint: ScopeEndpoint::new(fref.clone()),
|
||||||
rdef: path.to_string(),
|
rdef: path.to_string(),
|
||||||
data: None,
|
app_data: None,
|
||||||
guards: Vec::new(),
|
guards: Vec::new(),
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
default: Rc::new(RefCell::new(None)),
|
default: None,
|
||||||
external: Vec::new(),
|
external: Vec::new(),
|
||||||
factory_ref: fref,
|
factory_ref: fref,
|
||||||
}
|
}
|
||||||
|
@ -155,10 +155,10 @@ where
|
||||||
///
|
///
|
||||||
/// Data of different types from parent contexts will still be accessible.
|
/// Data of different types from parent contexts will still be accessible.
|
||||||
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
if self.data.is_none() {
|
if self.app_data.is_none() {
|
||||||
self.data = Some(Extensions::new());
|
self.app_data = Some(Extensions::new());
|
||||||
}
|
}
|
||||||
self.data.as_mut().unwrap().insert(data);
|
self.app_data.as_mut().unwrap().insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,15 +201,15 @@ where
|
||||||
self.external.extend(cfg.external);
|
self.external.extend(cfg.external);
|
||||||
|
|
||||||
if !cfg.data.is_empty() {
|
if !cfg.data.is_empty() {
|
||||||
let mut data = self.data.unwrap_or_else(Extensions::new);
|
let mut data = self.app_data.unwrap_or_else(Extensions::new);
|
||||||
|
|
||||||
for value in cfg.data.iter() {
|
for value in cfg.data.iter() {
|
||||||
value.create(&mut data);
|
value.create(&mut data);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.data = Some(data);
|
self.app_data = Some(data);
|
||||||
}
|
}
|
||||||
self.data
|
self.app_data
|
||||||
.get_or_insert_with(Extensions::new)
|
.get_or_insert_with(Extensions::new)
|
||||||
.extend(cfg.extensions);
|
.extend(cfg.extensions);
|
||||||
self
|
self
|
||||||
|
@ -295,11 +295,9 @@ where
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
// create and configure default resource
|
// create and configure default resource
|
||||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
||||||
f.into_factory().map_init_err(|e| {
|
|e| log::error!("Can not construct default service: {:?}", e),
|
||||||
log::error!("Can not construct default service: {:?}", e)
|
))));
|
||||||
}),
|
|
||||||
)))));
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -337,7 +335,7 @@ where
|
||||||
Scope {
|
Scope {
|
||||||
endpoint: apply(mw, self.endpoint),
|
endpoint: apply(mw, self.endpoint),
|
||||||
rdef: self.rdef,
|
rdef: self.rdef,
|
||||||
data: self.data,
|
app_data: self.app_data,
|
||||||
guards: self.guards,
|
guards: self.guards,
|
||||||
services: self.services,
|
services: self.services,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
|
@ -397,7 +395,7 @@ where
|
||||||
Scope {
|
Scope {
|
||||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||||
rdef: self.rdef,
|
rdef: self.rdef,
|
||||||
data: self.data,
|
app_data: self.app_data,
|
||||||
guards: self.guards,
|
guards: self.guards,
|
||||||
services: self.services,
|
services: self.services,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
|
@ -419,9 +417,7 @@ where
|
||||||
{
|
{
|
||||||
fn register(mut self, config: &mut AppService) {
|
fn register(mut self, config: &mut AppService) {
|
||||||
// update default resource if needed
|
// update default resource if needed
|
||||||
if self.default.borrow().is_none() {
|
let default = self.default.unwrap_or_else(|| config.default_service());
|
||||||
*self.default.borrow_mut() = Some(config.default_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
// register nested services
|
// register nested services
|
||||||
let mut cfg = config.clone_config();
|
let mut cfg = config.clone_config();
|
||||||
|
@ -437,14 +433,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom app data storage
|
// custom app data storage
|
||||||
if let Some(ref mut ext) = self.data {
|
if let Some(ref mut ext) = self.app_data {
|
||||||
config.set_service_data(ext);
|
config.set_service_data(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete scope pipeline creation
|
// complete scope pipeline creation
|
||||||
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
||||||
data: self.data.take().map(Rc::new),
|
app_data: self.app_data.take().map(Rc::new),
|
||||||
default: self.default.clone(),
|
default,
|
||||||
services: cfg
|
services: cfg
|
||||||
.into_services()
|
.into_services()
|
||||||
.1
|
.1
|
||||||
|
@ -476,129 +472,65 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeFactory {
|
pub struct ScopeFactory {
|
||||||
data: Option<Rc<Extensions>>,
|
app_data: Option<Rc<Extensions>>,
|
||||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
default: Rc<HttpNewService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
type Config = ();
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type InitError = ();
|
type Config = ();
|
||||||
type Service = ScopeService;
|
type Service = ScopeService;
|
||||||
type Future = ScopeFactoryResponse;
|
type InitError = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let default_fut = if let Some(ref default) = *self.default.borrow() {
|
// construct default service factory future
|
||||||
Some(default.new_service(()))
|
let default_fut = self.default.new_service(());
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
ScopeFactoryResponse {
|
// construct all services factory future with it's resource def and guards.
|
||||||
fut: self
|
let factory_fut =
|
||||||
.services
|
join_all(self.services.iter().map(|(path, factory, guards)| {
|
||||||
.iter()
|
let path = path.clone();
|
||||||
.map(|(path, service, guards)| {
|
let guards = guards.borrow_mut().take();
|
||||||
CreateScopeServiceItem::Future(
|
let factory_fut = factory.new_service(());
|
||||||
Some(path.clone()),
|
async move {
|
||||||
guards.borrow_mut().take(),
|
let service = factory_fut.await?;
|
||||||
service.new_service(()),
|
Ok((path, guards, service))
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
default: None,
|
|
||||||
data: self.data.clone(),
|
|
||||||
default_fut,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
/// Create scope service
|
let app_data = self.app_data.clone();
|
||||||
#[doc(hidden)]
|
|
||||||
#[pin_project::pin_project]
|
|
||||||
pub struct ScopeFactoryResponse {
|
|
||||||
fut: Vec<CreateScopeServiceItem>,
|
|
||||||
data: Option<Rc<Extensions>>,
|
|
||||||
default: Option<HttpService>,
|
|
||||||
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpServiceFut = LocalBoxFuture<'static, Result<HttpService, ()>>;
|
Box::pin(async move {
|
||||||
|
let default = default_fut.await?;
|
||||||
|
|
||||||
enum CreateScopeServiceItem {
|
// build router from the factory future result.
|
||||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
|
let router = factory_fut
|
||||||
Service(ResourceDef, Option<Guards>, HttpService),
|
.await
|
||||||
}
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
impl Future for ScopeFactoryResponse {
|
|
||||||
type Output = Result<ScopeService, ()>;
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut done = true;
|
|
||||||
|
|
||||||
if let Some(ref mut fut) = self.default_fut {
|
|
||||||
match Pin::new(fut).poll(cx)? {
|
|
||||||
Poll::Ready(default) => self.default = Some(default),
|
|
||||||
Poll::Pending => done = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// poll http services
|
|
||||||
for item in &mut self.fut {
|
|
||||||
let res = match item {
|
|
||||||
CreateScopeServiceItem::Future(
|
|
||||||
ref mut path,
|
|
||||||
ref mut guards,
|
|
||||||
ref mut fut,
|
|
||||||
) => match Pin::new(fut).poll(cx)? {
|
|
||||||
Poll::Ready(service) => {
|
|
||||||
Some((path.take().unwrap(), guards.take(), service))
|
|
||||||
}
|
|
||||||
Poll::Pending => {
|
|
||||||
done = false;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CreateScopeServiceItem::Service(_, _, _) => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((path, guards, service)) = res {
|
|
||||||
*item = CreateScopeServiceItem::Service(path, guards, service);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
let router = self
|
|
||||||
.fut
|
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.fold(Router::build(), |mut router, item| {
|
.fold(Router::build(), |mut router, (path, guards, service)| {
|
||||||
match item {
|
|
||||||
CreateScopeServiceItem::Service(path, guards, service) => {
|
|
||||||
router.rdef(path, service).2 = guards;
|
router.rdef(path, service).2 = guards;
|
||||||
}
|
|
||||||
CreateScopeServiceItem::Future(_, _, _) => unreachable!(),
|
|
||||||
}
|
|
||||||
router
|
router
|
||||||
});
|
})
|
||||||
Poll::Ready(Ok(ScopeService {
|
.finish();
|
||||||
data: self.data.clone(),
|
|
||||||
router: router.finish(),
|
Ok(ScopeService {
|
||||||
default: self.default.take(),
|
app_data,
|
||||||
_ready: None,
|
router,
|
||||||
}))
|
default,
|
||||||
} else {
|
})
|
||||||
Poll::Pending
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeService {
|
pub struct ScopeService {
|
||||||
data: Option<Rc<Extensions>>,
|
app_data: Option<Rc<Extensions>>,
|
||||||
router: Router<HttpService, Vec<Box<dyn Guard>>>,
|
router: Router<HttpService, Vec<Box<dyn Guard>>>,
|
||||||
default: Option<HttpService>,
|
default: HttpService,
|
||||||
_ready: Option<(ServiceRequest, ResourceInfo)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for ScopeService {
|
impl Service<ServiceRequest> for ScopeService {
|
||||||
|
@ -606,9 +538,7 @@ impl Service<ServiceRequest> for ScopeService {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
||||||
|
@ -622,21 +552,14 @@ impl Service<ServiceRequest> for ScopeService {
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(ref app_data) = self.app_data {
|
||||||
|
req.add_data_container(app_data.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((srv, _info)) = res {
|
if let Some((srv, _info)) = res {
|
||||||
if let Some(ref data) = self.data {
|
|
||||||
req.add_data_container(data.clone());
|
|
||||||
}
|
|
||||||
srv.call(req)
|
srv.call(req)
|
||||||
} else if let Some(ref mut default) = self.default {
|
|
||||||
if let Some(ref data) = self.data {
|
|
||||||
req.add_data_container(data.clone());
|
|
||||||
}
|
|
||||||
default.call(req)
|
|
||||||
} else {
|
} else {
|
||||||
let req = req.into_parts().0;
|
self.default.call(req)
|
||||||
Box::pin(async {
|
|
||||||
Ok(ServiceResponse::new(req, Response::NotFound().finish()))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -658,7 +581,7 @@ impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
|
||||||
type Config = ();
|
type Config = ();
|
||||||
type Service = ScopeService;
|
type Service = ScopeService;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Future = ScopeFactoryResponse;
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
self.factory.borrow_mut().as_mut().unwrap().new_service(())
|
self.factory.borrow_mut().as_mut().unwrap().new_service(())
|
||||||
|
|
|
@ -283,11 +283,7 @@ where
|
||||||
lst,
|
lst,
|
||||||
move || {
|
move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let cfg = AppConfig::new(
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
false,
|
|
||||||
addr,
|
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -302,7 +298,9 @@ where
|
||||||
svc
|
svc
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
svc.finish(map_config(factory(), move |_| {
|
||||||
|
AppConfig::new(false, addr, host.clone())
|
||||||
|
}))
|
||||||
.tcp()
|
.tcp()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -342,11 +340,7 @@ where
|
||||||
lst,
|
lst,
|
||||||
move || {
|
move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let cfg = AppConfig::new(
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
true,
|
|
||||||
addr,
|
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -361,7 +355,9 @@ where
|
||||||
svc
|
svc
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
svc.finish(map_config(factory(), move |_| {
|
||||||
|
AppConfig::new(true, addr, host.clone())
|
||||||
|
}))
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -401,11 +397,7 @@ where
|
||||||
lst,
|
lst,
|
||||||
move || {
|
move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let cfg = AppConfig::new(
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
true,
|
|
||||||
addr,
|
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -420,7 +412,9 @@ where
|
||||||
svc
|
svc
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
svc.finish(map_config(factory(), move |_| {
|
||||||
|
AppConfig::new(true, addr, host.clone())
|
||||||
|
}))
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
105
src/service.rs
105
src/service.rs
|
@ -52,75 +52,61 @@ where
|
||||||
/// An service http request
|
/// An service http request
|
||||||
///
|
///
|
||||||
/// ServiceRequest allows mutable access to request's internal structures
|
/// ServiceRequest allows mutable access to request's internal structures
|
||||||
pub struct ServiceRequest(HttpRequest);
|
pub struct ServiceRequest {
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Payload,
|
||||||
|
}
|
||||||
|
|
||||||
impl ServiceRequest {
|
impl ServiceRequest {
|
||||||
/// Construct service request
|
/// Construct service request
|
||||||
pub(crate) fn new(req: HttpRequest) -> Self {
|
pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
|
||||||
ServiceRequest(req)
|
Self { req, payload }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deconstruct request into parts
|
/// Deconstruct request into parts
|
||||||
pub fn into_parts(mut self) -> (HttpRequest, Payload) {
|
#[inline]
|
||||||
let pl = Rc::get_mut(&mut (self.0).inner).unwrap().payload.take();
|
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||||
(self.0, pl)
|
(self.req, self.payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct request from parts.
|
/// Construct request from parts.
|
||||||
///
|
pub fn from_parts(req: HttpRequest, payload: Payload) -> Self {
|
||||||
/// `ServiceRequest` can be re-constructed only if `req` hasn't been cloned.
|
Self { req, payload }
|
||||||
pub fn from_parts(
|
|
||||||
mut req: HttpRequest,
|
|
||||||
pl: Payload,
|
|
||||||
) -> Result<Self, (HttpRequest, Payload)> {
|
|
||||||
match Rc::get_mut(&mut req.inner) {
|
|
||||||
Some(p) => {
|
|
||||||
p.payload = pl;
|
|
||||||
Ok(ServiceRequest(req))
|
|
||||||
}
|
|
||||||
None => Err((req, pl)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct request from request.
|
/// Construct request from request.
|
||||||
///
|
///
|
||||||
/// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest`
|
/// The returned `ServiceRequest` would have no payload.
|
||||||
/// can be re-constructed only if rc's strong pointers count eq 1 and
|
pub fn from_request(req: HttpRequest) -> Self {
|
||||||
/// weak pointers count is 0.
|
ServiceRequest {
|
||||||
pub fn from_request(req: HttpRequest) -> Result<Self, HttpRequest> {
|
req,
|
||||||
// There is no weak pointer used on HttpRequest so intentionally
|
payload: Payload::None,
|
||||||
// ignore the check.
|
|
||||||
if Rc::strong_count(&req.inner) == 1 {
|
|
||||||
debug_assert!(Rc::weak_count(&req.inner) == 0);
|
|
||||||
Ok(ServiceRequest(req))
|
|
||||||
} else {
|
|
||||||
Err(req)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create service response
|
/// Create service response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
||||||
ServiceResponse::new(self.0, res.into())
|
ServiceResponse::new(self.req, res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create service response for error
|
/// Create service response for error
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn error_response<B, E: Into<Error>>(self, err: E) -> ServiceResponse<B> {
|
pub fn error_response<B, E: Into<Error>>(self, err: E) -> ServiceResponse<B> {
|
||||||
let res: Response = err.into().into();
|
let res: Response = err.into().into();
|
||||||
ServiceResponse::new(self.0, res.into_body())
|
ServiceResponse::new(self.req, res.into_body())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns reference to the request head
|
/// This method returns reference to the request head
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn head(&self) -> &RequestHead {
|
pub fn head(&self) -> &RequestHead {
|
||||||
&self.0.head()
|
&self.req.head()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns reference to the request head
|
/// This method returns reference to the request head
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||||
self.0.head_mut()
|
self.req.head_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's uri.
|
/// Request's uri.
|
||||||
|
@ -196,42 +182,42 @@ impl ServiceRequest {
|
||||||
/// access the matched value for that segment.
|
/// access the matched value for that segment.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_info(&self) -> &Path<Url> {
|
pub fn match_info(&self) -> &Path<Url> {
|
||||||
self.0.match_info()
|
self.req.match_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()).
|
/// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_name(&self) -> Option<&str> {
|
pub fn match_name(&self) -> Option<&str> {
|
||||||
self.0.match_name()
|
self.req.match_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()).
|
/// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_pattern(&self) -> Option<String> {
|
pub fn match_pattern(&self) -> Option<String> {
|
||||||
self.0.match_pattern()
|
self.req.match_pattern()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Get a mutable reference to the Path parameters.
|
/// Get a mutable reference to the Path parameters.
|
||||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||||
self.0.match_info_mut()
|
self.req.match_info_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Get a reference to a `ResourceMap` of current application.
|
/// Get a reference to a `ResourceMap` of current application.
|
||||||
pub fn resource_map(&self) -> &ResourceMap {
|
pub fn resource_map(&self) -> &ResourceMap {
|
||||||
self.0.resource_map()
|
self.req.resource_map()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service configuration
|
/// Service configuration
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn app_config(&self) -> &AppConfig {
|
pub fn app_config(&self) -> &AppConfig {
|
||||||
self.0.app_config()
|
self.req.app_config()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()).
|
/// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()).
|
||||||
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||||
for container in (self.0).inner.app_data.iter().rev() {
|
for container in self.req.inner.app_data.iter().rev() {
|
||||||
if let Some(data) = container.get::<T>() {
|
if let Some(data) = container.get::<T>() {
|
||||||
return Some(data);
|
return Some(data);
|
||||||
}
|
}
|
||||||
|
@ -242,13 +228,13 @@ impl ServiceRequest {
|
||||||
|
|
||||||
/// Set request payload.
|
/// Set request payload.
|
||||||
pub fn set_payload(&mut self, payload: Payload) {
|
pub fn set_payload(&mut self, payload: Payload) {
|
||||||
Rc::get_mut(&mut (self.0).inner).unwrap().payload = payload;
|
self.payload = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Add app data container to request's resolution set.
|
/// Add app data container to request's resolution set.
|
||||||
pub fn add_data_container(&mut self, extensions: Rc<Extensions>) {
|
pub fn add_data_container(&mut self, extensions: Rc<Extensions>) {
|
||||||
Rc::get_mut(&mut (self.0).inner)
|
Rc::get_mut(&mut (self.req).inner)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.app_data
|
.app_data
|
||||||
.push(extensions);
|
.push(extensions);
|
||||||
|
@ -273,18 +259,18 @@ impl HttpMessage for ServiceRequest {
|
||||||
/// Request extensions
|
/// Request extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.0.extensions()
|
self.req.extensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions
|
/// Mutable reference to a the request's extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.0.extensions_mut()
|
self.req.extensions_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn take_payload(&mut self) -> Payload<Self::Stream> {
|
fn take_payload(&mut self) -> Payload<Self::Stream> {
|
||||||
Rc::get_mut(&mut (self.0).inner).unwrap().payload.take()
|
self.payload.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,9 +402,9 @@ impl<B> ServiceResponse<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> Into<Response<B>> for ServiceResponse<B> {
|
impl<B> From<ServiceResponse<B>> for Response<B> {
|
||||||
fn into(self) -> Response<B> {
|
fn from(res: ServiceResponse<B>) -> Response<B> {
|
||||||
self.response
|
res.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,27 +538,6 @@ mod tests {
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_service_request() {
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
|
||||||
let (r, pl) = req.into_parts();
|
|
||||||
assert!(ServiceRequest::from_parts(r, pl).is_ok());
|
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
|
||||||
let (r, pl) = req.into_parts();
|
|
||||||
let _r2 = r.clone();
|
|
||||||
assert!(ServiceRequest::from_parts(r, pl).is_err());
|
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
|
||||||
let (r, _pl) = req.into_parts();
|
|
||||||
assert!(ServiceRequest::from_request(r).is_ok());
|
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
|
||||||
let (r, _pl) = req.into_parts();
|
|
||||||
let _r2 = r.clone();
|
|
||||||
assert!(ServiceRequest::from_request(r).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_service() {
|
async fn test_service() {
|
||||||
let mut srv = init_service(
|
let mut srv = init_service(
|
||||||
|
|
44
src/test.rs
44
src/test.rs
|
@ -27,10 +27,10 @@ use socket2::{Domain, Protocol, Socket, Type};
|
||||||
|
|
||||||
pub use actix_http::test::TestBuffer;
|
pub use actix_http::test::TestBuffer;
|
||||||
|
|
||||||
|
use crate::app_service::AppInitServiceState;
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
use crate::dev::{Body, MessageBody, Payload, Server};
|
use crate::dev::{Body, MessageBody, Payload, Server};
|
||||||
use crate::request::HttpRequestPool;
|
|
||||||
use crate::rmap::ResourceMap;
|
use crate::rmap::ResourceMap;
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
use crate::service::{ServiceRequest, ServiceResponse};
|
||||||
use crate::{Error, HttpRequest, HttpResponse};
|
use crate::{Error, HttpRequest, HttpResponse};
|
||||||
|
@ -542,15 +542,13 @@ impl TestRequest {
|
||||||
head.peer_addr = self.peer_addr;
|
head.peer_addr = self.peer_addr;
|
||||||
self.path.get_mut().update(&head.uri);
|
self.path.get_mut().update(&head.uri);
|
||||||
|
|
||||||
ServiceRequest::new(HttpRequest::new(
|
let app_state =
|
||||||
self.path,
|
AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
|
||||||
head,
|
|
||||||
|
ServiceRequest::new(
|
||||||
|
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)),
|
||||||
payload,
|
payload,
|
||||||
Rc::new(self.rmap),
|
)
|
||||||
self.config.clone(),
|
|
||||||
Rc::new(self.app_data),
|
|
||||||
HttpRequestPool::create(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request creation and generate `ServiceResponse` instance
|
/// Complete request creation and generate `ServiceResponse` instance
|
||||||
|
@ -560,19 +558,14 @@ impl TestRequest {
|
||||||
|
|
||||||
/// Complete request creation and generate `HttpRequest` instance
|
/// Complete request creation and generate `HttpRequest` instance
|
||||||
pub fn to_http_request(mut self) -> HttpRequest {
|
pub fn to_http_request(mut self) -> HttpRequest {
|
||||||
let (mut head, payload) = self.req.finish().into_parts();
|
let (mut head, _) = self.req.finish().into_parts();
|
||||||
head.peer_addr = self.peer_addr;
|
head.peer_addr = self.peer_addr;
|
||||||
self.path.get_mut().update(&head.uri);
|
self.path.get_mut().update(&head.uri);
|
||||||
|
|
||||||
HttpRequest::new(
|
let app_state =
|
||||||
self.path,
|
AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
|
||||||
head,
|
|
||||||
payload,
|
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data))
|
||||||
Rc::new(self.rmap),
|
|
||||||
self.config.clone(),
|
|
||||||
Rc::new(self.app_data),
|
|
||||||
HttpRequestPool::create(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request creation and generate `HttpRequest` and `Payload` instances
|
/// Complete request creation and generate `HttpRequest` and `Payload` instances
|
||||||
|
@ -581,15 +574,10 @@ impl TestRequest {
|
||||||
head.peer_addr = self.peer_addr;
|
head.peer_addr = self.peer_addr;
|
||||||
self.path.get_mut().update(&head.uri);
|
self.path.get_mut().update(&head.uri);
|
||||||
|
|
||||||
let req = HttpRequest::new(
|
let app_state =
|
||||||
self.path,
|
AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
|
||||||
head,
|
|
||||||
Payload::None,
|
let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data));
|
||||||
Rc::new(self.rmap),
|
|
||||||
self.config.clone(),
|
|
||||||
Rc::new(self.app_data),
|
|
||||||
HttpRequestPool::create(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(req, payload)
|
(req, payload)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,150 +1,184 @@
|
||||||
use std::{
|
//! For either helper, see [`Either`].
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
use bytes::Bytes;
|
||||||
task::{Context, Poll},
|
use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
dev,
|
||||||
|
web::{Form, Json},
|
||||||
|
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{Error, Response};
|
/// Combines two extractor or responder types into a single type.
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt};
|
|
||||||
use pin_project::pin_project;
|
|
||||||
|
|
||||||
use crate::{dev, request::HttpRequest, FromRequest, Responder};
|
|
||||||
|
|
||||||
/// Combines two different responder types into a single type
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// Can be converted to and from an [`either::Either`].
|
||||||
/// use actix_web::{Either, Error, HttpResponse};
|
|
||||||
///
|
///
|
||||||
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
|
/// # Extractor
|
||||||
|
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
|
||||||
|
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
|
||||||
///
|
///
|
||||||
/// fn index() -> RegisterResult {
|
/// It is important to note that this extractor, by necessity, buffers the entire request payload
|
||||||
/// if is_a_variant() {
|
/// as part of its implementation. Though, it does respect any `PayloadConfig` maximum size limits.
|
||||||
/// // <- choose left variant
|
///
|
||||||
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
|
/// ```
|
||||||
|
/// use actix_web::{post, web, Either};
|
||||||
|
/// use serde::Deserialize;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // handler that accepts form as JSON or form-urlencoded.
|
||||||
|
/// #[post("/")]
|
||||||
|
/// async fn index(form: Either<web::Json<Info>, web::Form<Info>>) -> String {
|
||||||
|
/// let name: String = match form {
|
||||||
|
/// Either::Left(json) => json.name.to_owned(),
|
||||||
|
/// Either::Right(form) => form.name.to_owned(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// format!("Welcome {}!", name)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Responder
|
||||||
|
/// It may be desireable to use a concrete type for a response with multiple branches. As long as
|
||||||
|
/// both types implement `Responder`, so will the `Either` type, enabling it to be used as a
|
||||||
|
/// handler's return type.
|
||||||
|
///
|
||||||
|
/// All properties of a response are determined by the Responder branch returned.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{get, Either, Error, HttpResponse};
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// async fn index() -> Either<&'static str, Result<HttpResponse, Error>> {
|
||||||
|
/// if 1 == 2 {
|
||||||
|
/// // respond with Left variant
|
||||||
|
/// Either::Left("Bad data")
|
||||||
/// } else {
|
/// } else {
|
||||||
/// Either::B(
|
/// // respond with Right variant
|
||||||
/// // <- Right variant
|
/// Either::Right(
|
||||||
/// Ok(HttpResponse::Ok()
|
/// Ok(HttpResponse::Ok()
|
||||||
/// .content_type("text/html")
|
/// .content_type(mime::TEXT_HTML)
|
||||||
/// .body("Hello!"))
|
/// .body("<p>Hello!</p>"))
|
||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// # fn is_a_variant() -> bool { true }
|
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Either<A, B> {
|
pub enum Either<L, R> {
|
||||||
/// First branch of the type
|
/// A value of type `L`.
|
||||||
A(A),
|
Left(L),
|
||||||
/// Second branch of the type
|
|
||||||
B(B),
|
/// A value of type `R`.
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Either<Form<T>, Json<T>> {
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
match self {
|
||||||
|
Either::Left(form) => form.into_inner(),
|
||||||
|
Either::Right(form) => form.into_inner(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Either<Json<T>, Form<T>> {
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
match self {
|
||||||
|
Either::Left(form) => form.into_inner(),
|
||||||
|
Either::Right(form) => form.into_inner(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> From<either::Either<L, R>> for Either<L, R> {
|
||||||
|
fn from(val: either::Either<L, R>) -> Self {
|
||||||
|
match val {
|
||||||
|
either::Either::Left(l) => Either::Left(l),
|
||||||
|
either::Either::Right(r) => Either::Right(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> From<Either<L, R>> for either::Either<L, R> {
|
||||||
|
fn from(val: Either<L, R>) -> Self {
|
||||||
|
match val {
|
||||||
|
Either::Left(l) => either::Either::Left(l),
|
||||||
|
Either::Right(r) => either::Either::Right(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl<A, B> Either<A, B> {
|
impl<L, R> Either<L, R> {
|
||||||
pub(self) fn unwrap_left(self) -> A {
|
pub(self) fn unwrap_left(self) -> L {
|
||||||
match self {
|
match self {
|
||||||
Either::A(data) => data,
|
Either::Left(data) => data,
|
||||||
Either::B(_) => {
|
Either::Right(_) => {
|
||||||
panic!("Cannot unwrap left branch. Either contains a right branch.")
|
panic!("Cannot unwrap Left branch. Either contains an `R` type.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(self) fn unwrap_right(self) -> B {
|
pub(self) fn unwrap_right(self) -> R {
|
||||||
match self {
|
match self {
|
||||||
Either::A(_) => {
|
Either::Left(_) => {
|
||||||
panic!("Cannot unwrap right branch. Either contains a left branch.")
|
panic!("Cannot unwrap Right branch. Either contains an `L` type.")
|
||||||
}
|
}
|
||||||
Either::B(data) => data,
|
Either::Right(data) => data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> Responder for Either<A, B>
|
/// See [here](#responder) for example of usage as a handler return type.
|
||||||
|
impl<L, R> Responder for Either<L, R>
|
||||||
where
|
where
|
||||||
A: Responder,
|
L: Responder,
|
||||||
B: Responder,
|
R: Responder,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
type Future = EitherResponder<A, B>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
match self {
|
match self {
|
||||||
Either::A(a) => EitherResponder::A(a.respond_to(req)),
|
Either::Left(a) => a.respond_to(req),
|
||||||
Either::B(b) => EitherResponder::B(b.respond_to(req)),
|
Either::Right(b) => b.respond_to(req),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = EitherResponderProj)]
|
/// A composite error resulting from failure to extract an `Either<L, R>`.
|
||||||
pub enum EitherResponder<A, B>
|
|
||||||
where
|
|
||||||
A: Responder,
|
|
||||||
B: Responder,
|
|
||||||
{
|
|
||||||
A(#[pin] A::Future),
|
|
||||||
B(#[pin] B::Future),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A, B> Future for EitherResponder<A, B>
|
|
||||||
where
|
|
||||||
A: Responder,
|
|
||||||
B: Responder,
|
|
||||||
{
|
|
||||||
type Output = Result<Response, Error>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
match self.project() {
|
|
||||||
EitherResponderProj::A(fut) => {
|
|
||||||
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
|
|
||||||
}
|
|
||||||
EitherResponderProj::B(fut) => {
|
|
||||||
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A composite error resulting from failure to extract an `Either<A, B>`.
|
|
||||||
///
|
///
|
||||||
/// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the
|
/// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the
|
||||||
/// error from the primary extractor. To access the fallback error, use a match clause.
|
/// error from the primary extractor. To access the fallback error, use a match clause.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EitherExtractError<A, B> {
|
pub enum EitherExtractError<L, R> {
|
||||||
/// Error from payload buffering, such as exceeding payload max size limit.
|
/// Error from payload buffering, such as exceeding payload max size limit.
|
||||||
Bytes(Error),
|
Bytes(Error),
|
||||||
|
|
||||||
/// Error from primary extractor.
|
/// Error from primary extractor.
|
||||||
Extract(A, B),
|
Extract(L, R),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> Into<Error> for EitherExtractError<A, B>
|
impl<L, R> From<EitherExtractError<L, R>> for Error
|
||||||
where
|
where
|
||||||
A: Into<Error>,
|
L: Into<Error>,
|
||||||
B: Into<Error>,
|
R: Into<Error>,
|
||||||
{
|
{
|
||||||
fn into(self) -> Error {
|
fn from(err: EitherExtractError<L, R>) -> Error {
|
||||||
match self {
|
match err {
|
||||||
EitherExtractError::Bytes(err) => err,
|
EitherExtractError::Bytes(err) => err,
|
||||||
EitherExtractError::Extract(a_err, _b_err) => a_err.into(),
|
EitherExtractError::Extract(a_err, _b_err) => a_err.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
|
/// See [here](#extractor) for example of usage as an extractor.
|
||||||
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
|
impl<L, R> FromRequest for Either<L, R>
|
||||||
///
|
|
||||||
/// It is important to note that this extractor, by necessity, buffers the entire request payload
|
|
||||||
/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit.
|
|
||||||
impl<A, B> FromRequest for Either<A, B>
|
|
||||||
where
|
where
|
||||||
A: FromRequest + 'static,
|
L: FromRequest + 'static,
|
||||||
B: FromRequest + 'static,
|
R: FromRequest + 'static,
|
||||||
{
|
{
|
||||||
type Error = EitherExtractError<A::Error, B::Error>;
|
type Error = EitherExtractError<L::Error, R::Error>;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
|
||||||
|
@ -153,32 +187,32 @@ where
|
||||||
|
|
||||||
Bytes::from_request(req, payload)
|
Bytes::from_request(req, payload)
|
||||||
.map_err(EitherExtractError::Bytes)
|
.map_err(EitherExtractError::Bytes)
|
||||||
.and_then(|bytes| bytes_to_a_or_b(req2, bytes))
|
.and_then(|bytes| bytes_to_l_or_r(req2, bytes))
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bytes_to_a_or_b<A, B>(
|
async fn bytes_to_l_or_r<L, R>(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
) -> Result<Either<A, B>, EitherExtractError<A::Error, B::Error>>
|
) -> Result<Either<L, R>, EitherExtractError<L::Error, R::Error>>
|
||||||
where
|
where
|
||||||
A: FromRequest + 'static,
|
L: FromRequest + 'static,
|
||||||
B: FromRequest + 'static,
|
R: FromRequest + 'static,
|
||||||
{
|
{
|
||||||
let fallback = bytes.clone();
|
let fallback = bytes.clone();
|
||||||
let a_err;
|
let a_err;
|
||||||
|
|
||||||
let mut pl = payload_from_bytes(bytes);
|
let mut pl = payload_from_bytes(bytes);
|
||||||
match A::from_request(&req, &mut pl).await {
|
match L::from_request(&req, &mut pl).await {
|
||||||
Ok(a_data) => return Ok(Either::A(a_data)),
|
Ok(a_data) => return Ok(Either::Left(a_data)),
|
||||||
// store A's error for returning if B also fails
|
// store A's error for returning if B also fails
|
||||||
Err(err) => a_err = err,
|
Err(err) => a_err = err,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pl = payload_from_bytes(fallback);
|
let mut pl = payload_from_bytes(fallback);
|
||||||
match B::from_request(&req, &mut pl).await {
|
match R::from_request(&req, &mut pl).await {
|
||||||
Ok(b_data) => return Ok(Either::B(b_data)),
|
Ok(b_data) => return Ok(Either::Right(b_data)),
|
||||||
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
|
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,68 @@
|
||||||
//! Form extractor
|
//! For URL encoded form helper documentation, see [`Form`].
|
||||||
|
|
||||||
use std::future::Future;
|
use std::{
|
||||||
use std::pin::Pin;
|
fmt,
|
||||||
use std::rc::Rc;
|
future::Future,
|
||||||
use std::task::{Context, Poll};
|
ops,
|
||||||
use std::{fmt, ops};
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_http::{Error, HttpMessage, Payload, Response};
|
use actix_http::Payload;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use encoding_rs::{Encoding, UTF_8};
|
use encoding_rs::{Encoding, UTF_8};
|
||||||
use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready};
|
use futures_util::{
|
||||||
use futures_util::StreamExt;
|
future::{FutureExt, LocalBoxFuture},
|
||||||
use serde::de::DeserializeOwned;
|
StreamExt,
|
||||||
use serde::Serialize;
|
};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
use crate::dev::Decompress;
|
use crate::dev::Decompress;
|
||||||
use crate::error::UrlencodedError;
|
use crate::{
|
||||||
use crate::extract::FromRequest;
|
error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web,
|
||||||
use crate::http::{
|
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
header::{ContentType, CONTENT_LENGTH},
|
|
||||||
StatusCode,
|
|
||||||
};
|
};
|
||||||
use crate::request::HttpRequest;
|
|
||||||
use crate::{responder::Responder, web};
|
|
||||||
|
|
||||||
/// Form data helper (`application/x-www-form-urlencoded`)
|
/// URL encoded payload extractor and responder.
|
||||||
///
|
///
|
||||||
/// Can be use to extract url-encoded data from the request body,
|
/// `Form` has two uses: URL encoded responses, and extracting typed data from URL request payloads.
|
||||||
/// or send url-encoded data as the response.
|
|
||||||
///
|
///
|
||||||
/// ## Extract
|
/// # Extractor
|
||||||
|
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||||
|
/// [`serde::Deserialize`] trait.
|
||||||
///
|
///
|
||||||
/// To extract typed information from request's body, the type `T` must
|
/// Use [`FormConfig`] to configure extraction process.
|
||||||
/// implement the `Deserialize` trait from *serde*.
|
|
||||||
///
|
///
|
||||||
/// [**FormConfig**](FormConfig) allows to configure extraction
|
/// ```
|
||||||
/// process.
|
/// use actix_web::{post, web};
|
||||||
///
|
/// use serde::Deserialize;
|
||||||
/// ### Example
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::web;
|
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct FormData {
|
/// struct Info {
|
||||||
/// username: String,
|
/// name: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// Extract form data using serde.
|
/// // This handler is only called if:
|
||||||
/// /// This handler get called only if content type is *x-www-form-urlencoded*
|
/// // - request headers declare the content type as `application/x-www-form-urlencoded`
|
||||||
/// /// and content of the request could be deserialized to a `FormData` struct
|
/// // - request payload is deserialized into a `Info` struct from the URL encoded format
|
||||||
/// fn index(form: web::Form<FormData>) -> String {
|
/// #[post("/")]
|
||||||
/// format!("Welcome {}!", form.username)
|
/// async fn index(form: web::Form<Info>) -> String {
|
||||||
|
/// format!("Welcome {}!", form.name)
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Respond
|
/// # Responder
|
||||||
|
/// The `Form` type also allows you to create URL encoded responses:
|
||||||
|
/// simply return a value of type Form<T> where T is the type to be URL encoded.
|
||||||
|
/// The type must implement [`serde::Serialize`].
|
||||||
///
|
///
|
||||||
/// The `Form` type also allows you to respond with well-formed url-encoded data:
|
/// Responses use
|
||||||
/// simply return a value of type Form<T> where T is the type to be url-encoded.
|
|
||||||
/// The type must implement `serde::Serialize`;
|
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ```
|
||||||
/// ```rust
|
/// use actix_web::{get, web};
|
||||||
/// use actix_web::*;
|
/// use serde::Serialize;
|
||||||
/// use serde_derive::Serialize;
|
|
||||||
///
|
///
|
||||||
/// #[derive(Serialize)]
|
/// #[derive(Serialize)]
|
||||||
/// struct SomeForm {
|
/// struct SomeForm {
|
||||||
|
@ -74,22 +70,23 @@ use crate::{responder::Responder, web};
|
||||||
/// age: u8
|
/// age: u8
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Will return a 200 response with header
|
/// // Response will have:
|
||||||
/// // `Content-Type: application/x-www-form-urlencoded`
|
/// // - status: 200 OK
|
||||||
/// // and body "name=actix&age=123"
|
/// // - header: `Content-Type: application/x-www-form-urlencoded`
|
||||||
/// fn index() -> web::Form<SomeForm> {
|
/// // - body: `name=actix&age=123`
|
||||||
|
/// #[get("/")]
|
||||||
|
/// async fn index() -> web::Form<SomeForm> {
|
||||||
/// web::Form(SomeForm {
|
/// web::Form(SomeForm {
|
||||||
/// name: "actix".into(),
|
/// name: "actix".into(),
|
||||||
/// age: 123
|
/// age: 123
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Form<T>(pub T);
|
pub struct Form<T>(pub T);
|
||||||
|
|
||||||
impl<T> Form<T> {
|
impl<T> Form<T> {
|
||||||
/// Deconstruct to an inner value
|
/// Unwrap into inner `T` value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -109,6 +106,7 @@ impl<T> ops::DerefMut for Form<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [here](#extractor) for example of usage as an extractor.
|
||||||
impl<T> FromRequest for Form<T>
|
impl<T> FromRequest for Form<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
|
@ -120,7 +118,7 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
let req2 = req.clone();
|
let req2 = req.clone();
|
||||||
let (limit, err) = req
|
let (limit, err_handler) = req
|
||||||
.app_data::<Self::Config>()
|
.app_data::<Self::Config>()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
req.app_data::<web::Data<Self::Config>>()
|
req.app_data::<web::Data<Self::Config>>()
|
||||||
|
@ -132,13 +130,10 @@ where
|
||||||
UrlEncoded::new(req, payload)
|
UrlEncoded::new(req, payload)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.map(move |res| match res {
|
.map(move |res| match res {
|
||||||
Err(e) => {
|
Err(err) => match err_handler {
|
||||||
if let Some(err) = err {
|
Some(err_handler) => Err((err_handler)(err, &req2)),
|
||||||
Err((*err)(e, &req2))
|
None => Err(err.into()),
|
||||||
} else {
|
},
|
||||||
Err(e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(item) => Ok(Form(item)),
|
Ok(item) => Ok(Form(item)),
|
||||||
})
|
})
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
|
@ -157,49 +152,39 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [here](#responder) for example of usage as a handler return type.
|
||||||
impl<T: Serialize> Responder for Form<T> {
|
impl<T: Serialize> Responder for Form<T> {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
match serde_urlencoded::to_string(&self.0) {
|
||||||
|
Ok(body) => HttpResponse::Ok()
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||||
let body = match serde_urlencoded::to_string(&self.0) {
|
.body(body),
|
||||||
Ok(body) => body,
|
Err(err) => HttpResponse::from_error(err.into()),
|
||||||
Err(e) => return err(e.into()),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ok(Response::build(StatusCode::OK)
|
|
||||||
.set(ContentType::form_url_encoded())
|
|
||||||
.body(body))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Form extractor configuration
|
/// [`Form`] extractor configuration.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_web::{web, App, FromRequest, Result};
|
/// use actix_web::{post, web, App, FromRequest, Result};
|
||||||
/// use serde_derive::Deserialize;
|
/// use serde::Deserialize;
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct FormData {
|
/// struct Info {
|
||||||
/// username: String,
|
/// username: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// Extract form data using serde.
|
/// // Custom `FormConfig` is applied to App.
|
||||||
/// /// Custom configuration is used for this handler, max payload size is 4k
|
/// // Max payload size for URL encoded forms is set to 4kB.
|
||||||
/// async fn index(form: web::Form<FormData>) -> Result<String> {
|
/// #[post("/")]
|
||||||
|
/// async fn index(form: web::Form<Info>) -> Result<String> {
|
||||||
/// Ok(format!("Welcome {}!", form.username))
|
/// Ok(format!("Welcome {}!", form.username))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// App::new()
|
||||||
/// let app = App::new().service(
|
/// .app_data(web::FormConfig::default().limit(4096))
|
||||||
/// web::resource("/index.html")
|
/// .service(index);
|
||||||
/// // change `Form` extractor configuration
|
|
||||||
/// .app_data(
|
|
||||||
/// web::FormConfig::default().limit(4097)
|
|
||||||
/// )
|
|
||||||
/// .route(web::get().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FormConfig {
|
pub struct FormConfig {
|
||||||
|
@ -208,7 +193,7 @@ pub struct FormConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormConfig {
|
impl FormConfig {
|
||||||
/// Change max size of payload. By default max size is 16Kb
|
/// Set maximum accepted payload size. By default this limit is 16kB.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.limit = limit;
|
self.limit = limit;
|
||||||
self
|
self
|
||||||
|
@ -233,33 +218,30 @@ impl Default for FormConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Future that resolves to a parsed urlencoded values.
|
/// Future that resolves to some `T` when parsed from a URL encoded payload.
|
||||||
///
|
///
|
||||||
/// Parse `application/x-www-form-urlencoded` encoded request's body.
|
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
|
||||||
/// Return `UrlEncoded` future. Form can be deserialized to any type that
|
|
||||||
/// implements `Deserialize` trait from *serde*.
|
|
||||||
///
|
///
|
||||||
/// Returns error:
|
/// Returns error if:
|
||||||
///
|
/// - content type is not `application/x-www-form-urlencoded`
|
||||||
/// * content type is not `application/x-www-form-urlencoded`
|
/// - content length is greater than [limit](UrlEncoded::limit())
|
||||||
/// * content-length is greater than 32k
|
pub struct UrlEncoded<T> {
|
||||||
///
|
|
||||||
pub struct UrlEncoded<U> {
|
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
stream: Option<Decompress<Payload>>,
|
stream: Option<Decompress<Payload>>,
|
||||||
#[cfg(not(feature = "compress"))]
|
#[cfg(not(feature = "compress"))]
|
||||||
stream: Option<Payload>,
|
stream: Option<Payload>,
|
||||||
|
|
||||||
limit: usize,
|
limit: usize,
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
encoding: &'static Encoding,
|
encoding: &'static Encoding,
|
||||||
err: Option<UrlencodedError>,
|
err: Option<UrlencodedError>,
|
||||||
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>,
|
fut: Option<LocalBoxFuture<'static, Result<T, UrlencodedError>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::borrow_interior_mutable_const)]
|
#[allow(clippy::borrow_interior_mutable_const)]
|
||||||
impl<U> UrlEncoded<U> {
|
impl<T> UrlEncoded<T> {
|
||||||
/// Create a new future to URL encode a request
|
/// Create a new future to decode a URL encoded request payload.
|
||||||
pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {
|
pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
|
||||||
// check content type
|
// check content type
|
||||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||||
return Self::err(UrlencodedError::ContentType);
|
return Self::err(UrlencodedError::ContentType);
|
||||||
|
@ -297,29 +279,29 @@ impl<U> UrlEncoded<U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err(e: UrlencodedError) -> Self {
|
fn err(err: UrlencodedError) -> Self {
|
||||||
UrlEncoded {
|
UrlEncoded {
|
||||||
stream: None,
|
stream: None,
|
||||||
limit: 32_768,
|
limit: 32_768,
|
||||||
fut: None,
|
fut: None,
|
||||||
err: Some(e),
|
err: Some(err),
|
||||||
length: None,
|
length: None,
|
||||||
encoding: UTF_8,
|
encoding: UTF_8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.limit = limit;
|
self.limit = limit;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U> Future for UrlEncoded<U>
|
impl<T> Future for UrlEncoded<T>
|
||||||
where
|
where
|
||||||
U: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<U, UrlencodedError>;
|
type Output = Result<T, UrlencodedError>;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
|
@ -348,6 +330,7 @@ where
|
||||||
|
|
||||||
while let Some(item) = stream.next().await {
|
while let Some(item) = stream.next().await {
|
||||||
let chunk = item?;
|
let chunk = item?;
|
||||||
|
|
||||||
if (body.len() + chunk.len()) > limit {
|
if (body.len() + chunk.len()) > limit {
|
||||||
return Err(UrlencodedError::Overflow {
|
return Err(UrlencodedError::Overflow {
|
||||||
size: body.len() + chunk.len(),
|
size: body.len() + chunk.len(),
|
||||||
|
@ -359,19 +342,21 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if encoding == UTF_8 {
|
if encoding == UTF_8 {
|
||||||
serde_urlencoded::from_bytes::<U>(&body)
|
serde_urlencoded::from_bytes::<T>(&body)
|
||||||
.map_err(|_| UrlencodedError::Parse)
|
.map_err(|_| UrlencodedError::Parse)
|
||||||
} else {
|
} else {
|
||||||
let body = encoding
|
let body = encoding
|
||||||
.decode_without_bom_handling_and_without_replacement(&body)
|
.decode_without_bom_handling_and_without_replacement(&body)
|
||||||
.map(|s| s.into_owned())
|
.map(|s| s.into_owned())
|
||||||
.ok_or(UrlencodedError::Parse)?;
|
.ok_or(UrlencodedError::Parse)?;
|
||||||
serde_urlencoded::from_str::<U>(&body)
|
|
||||||
|
serde_urlencoded::from_str::<T>(&body)
|
||||||
.map_err(|_| UrlencodedError::Parse)
|
.map_err(|_| UrlencodedError::Parse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.boxed_local(),
|
.boxed_local(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,7 +367,10 @@ mod tests {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
use crate::http::{
|
||||||
|
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
||||||
|
@ -493,7 +481,7 @@ mod tests {
|
||||||
hello: "world".to_string(),
|
hello: "world".to_string(),
|
||||||
counter: 123,
|
counter: 123,
|
||||||
});
|
});
|
||||||
let resp = form.respond_to(&req).await.unwrap();
|
let resp = form.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
|
@ -519,6 +507,6 @@ mod tests {
|
||||||
assert!(s.is_err());
|
assert!(s.is_err());
|
||||||
|
|
||||||
let err_str = s.err().unwrap().to_string();
|
let err_str = s.err().unwrap().to_string();
|
||||||
assert!(err_str.contains("Urlencoded payload size is bigger"));
|
assert!(err_str.starts_with("URL encoded payload is larger"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,44 @@
|
||||||
//! Json extractor/responder
|
//! For JSON helper documentation, see [`Json`].
|
||||||
|
|
||||||
use std::future::Future;
|
use std::{
|
||||||
use std::marker::PhantomData;
|
fmt,
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::sync::Arc;
|
marker::PhantomData,
|
||||||
use std::task::{Context, Poll};
|
ops,
|
||||||
use std::{fmt, ops};
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::{ready, stream::Stream};
|
||||||
use futures_util::ready;
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use futures_util::stream::Stream;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use actix_http::http::{header::CONTENT_LENGTH, StatusCode};
|
use actix_http::Payload;
|
||||||
use actix_http::{HttpMessage, Payload, Response};
|
|
||||||
|
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
use crate::dev::Decompress;
|
use crate::dev::Decompress;
|
||||||
use crate::error::{Error, JsonPayloadError};
|
use crate::{
|
||||||
use crate::extract::FromRequest;
|
error::{Error, JsonPayloadError},
|
||||||
use crate::request::HttpRequest;
|
extract::FromRequest,
|
||||||
use crate::{responder::Responder, web};
|
http::header::CONTENT_LENGTH,
|
||||||
|
request::HttpRequest,
|
||||||
|
web, HttpMessage, HttpResponse, Responder,
|
||||||
|
};
|
||||||
|
|
||||||
/// Json helper
|
/// JSON extractor and responder.
|
||||||
///
|
///
|
||||||
/// Json can be used for two different purpose. First is for json response
|
/// `Json` has two uses: JSON responses, and extracting typed data from JSON request payloads.
|
||||||
/// generation and second is for extracting typed information from request's
|
|
||||||
/// payload.
|
|
||||||
///
|
///
|
||||||
/// To extract typed information from request's body, the type `T` must
|
/// # Extractor
|
||||||
/// implement the `Deserialize` trait from *serde*.
|
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||||
|
/// [`serde::Deserialize`] trait.
|
||||||
///
|
///
|
||||||
/// [**JsonConfig**](JsonConfig) allows to configure extraction
|
/// Use [`JsonConfig`] to configure extraction process.
|
||||||
/// process.
|
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ```
|
||||||
///
|
/// use actix_web::{post, web, App};
|
||||||
/// ```rust
|
/// use serde::Deserialize;
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
|
@ -48,43 +46,37 @@ use crate::{responder::Responder, web};
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// deserialize `Info` from request's body
|
/// /// deserialize `Info` from request's body
|
||||||
|
/// #[post("/")]
|
||||||
/// async fn index(info: web::Json<Info>) -> String {
|
/// async fn index(info: web::Json<Info>) -> String {
|
||||||
/// format!("Welcome {}!", info.username)
|
/// format!("Welcome {}!", info.username)
|
||||||
/// }
|
/// }
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(
|
|
||||||
/// web::post().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The `Json` type allows you to respond with well-formed JSON data: simply
|
/// # Responder
|
||||||
/// return a value of type Json<T> where T is the type of a structure
|
/// The `Json` type JSON formatted responses. A handler may return a value of type
|
||||||
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
|
/// `Json<T>` where `T` is the type of a structure to serialize into JSON. The type `T` must
|
||||||
/// trait from *serde*.
|
/// implement [`serde::Serialize`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_web::*;
|
/// use actix_web::{post, web, HttpRequest};
|
||||||
/// use serde_derive::Serialize;
|
/// use serde::Serialize;
|
||||||
///
|
///
|
||||||
/// #[derive(Serialize)]
|
/// #[derive(Serialize)]
|
||||||
/// struct MyObj {
|
/// struct Info {
|
||||||
/// name: String,
|
/// name: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn index(req: HttpRequest) -> Result<web::Json<MyObj>> {
|
/// #[post("/{name}")]
|
||||||
/// Ok(web::Json(MyObj {
|
/// async fn index(req: HttpRequest) -> web::Json<Info> {
|
||||||
/// name: req.match_info().get("name").unwrap().to_string(),
|
/// web::Json(Info {
|
||||||
/// }))
|
/// name: req.match_info().get("name").unwrap().to_owned(),
|
||||||
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Json<T>(pub T);
|
pub struct Json<T>(pub T);
|
||||||
|
|
||||||
impl<T> Json<T> {
|
impl<T> Json<T> {
|
||||||
/// Deconstruct to an inner value
|
/// Unwrap into inner `T` value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -122,54 +114,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates response with OK status code, correct content type header, and serialized JSON payload.
|
||||||
|
///
|
||||||
|
/// If serialization failed
|
||||||
impl<T: Serialize> Responder for Json<T> {
|
impl<T: Serialize> Responder for Json<T> {
|
||||||
type Error = Error;
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<Response, Error>>;
|
match serde_json::to_string(&self.0) {
|
||||||
|
Ok(body) => HttpResponse::Ok()
|
||||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
.content_type(mime::APPLICATION_JSON)
|
||||||
let body = match serde_json::to_string(&self.0) {
|
.body(body),
|
||||||
Ok(body) => body,
|
Err(err) => HttpResponse::from_error(err.into()),
|
||||||
Err(e) => return ready(Err(e.into())),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ready(Ok(Response::build(StatusCode::OK)
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(body)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Json extractor. Allow to extract typed information from request's
|
/// See [here](#extractor) for example of usage as an extractor.
|
||||||
/// payload.
|
|
||||||
///
|
|
||||||
/// To extract typed information from request's body, the type `T` must
|
|
||||||
/// implement the `Deserialize` trait from *serde*.
|
|
||||||
///
|
|
||||||
/// [**JsonConfig**](JsonConfig) allows to configure extraction
|
|
||||||
/// process.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
|
||||||
/// #[derive(Deserialize)]
|
|
||||||
/// struct Info {
|
|
||||||
/// username: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// /// deserialize `Info` from request's body
|
|
||||||
/// async fn index(info: web::Json<Info>) -> String {
|
|
||||||
/// format!("Welcome {}!", info.username)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(
|
|
||||||
/// web::post().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl<T> FromRequest for Json<T>
|
impl<T> FromRequest for Json<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
|
@ -215,7 +174,7 @@ where
|
||||||
let res = ready!(Pin::new(&mut this.fut).poll(cx));
|
let res = ready!(Pin::new(&mut this.fut).poll(cx));
|
||||||
|
|
||||||
let res = match res {
|
let res = match res {
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
let req = this.req.take().unwrap();
|
let req = this.req.take().unwrap();
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Failed to deserialize Json from payload. \
|
"Failed to deserialize Json from payload. \
|
||||||
|
@ -223,10 +182,10 @@ where
|
||||||
req.path()
|
req.path()
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(err) = this.err_handler.as_ref() {
|
if let Some(err_handler) = this.err_handler.as_ref() {
|
||||||
Err((*err)(e, &req))
|
Err((*err_handler)(err, &req))
|
||||||
} else {
|
} else {
|
||||||
Err(e.into())
|
Err(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(data) => Ok(Json(data)),
|
Ok(data) => Ok(Json(data)),
|
||||||
|
@ -236,44 +195,39 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Json extractor configuration
|
/// `Json` extractor configuration.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Usage
|
||||||
///
|
/// ```
|
||||||
/// ```rust
|
/// use actix_web::{error, post, web, App, FromRequest, HttpResponse};
|
||||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
/// use serde::Deserialize;
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
/// username: String,
|
/// name: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// deserialize `Info` from request's body, max payload size is 4kb
|
/// // `Json` extraction is bound by custom `JsonConfig` applied to App.
|
||||||
|
/// #[post("/")]
|
||||||
/// async fn index(info: web::Json<Info>) -> String {
|
/// async fn index(info: web::Json<Info>) -> String {
|
||||||
/// format!("Welcome {}!", info.username)
|
/// format!("Welcome {}!", info.name)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// // custom `Json` extractor configuration
|
||||||
/// let app = App::new().service(
|
/// let json_cfg = web::JsonConfig::default()
|
||||||
/// web::resource("/index.html")
|
/// // limit request payload size
|
||||||
/// .app_data(
|
/// .limit(4096)
|
||||||
/// // Json extractor configuration for this resource.
|
/// // only accept text/plain content type
|
||||||
/// web::JsonConfig::default()
|
/// .content_type(|mime| mime == mime::TEXT_PLAIN)
|
||||||
/// .limit(4096) // Limit request payload size
|
/// // use custom error handler
|
||||||
/// .content_type(|mime| { // <- accept text/plain content type
|
/// .error_handler(|err, req| {
|
||||||
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||||
/// })
|
/// });
|
||||||
/// .error_handler(|err, req| { // <- create custom error response
|
///
|
||||||
/// error::InternalError::from_response(
|
/// App::new()
|
||||||
/// err, HttpResponse::Conflict().finish()).into()
|
/// .app_data(json_cfg)
|
||||||
/// })
|
/// .service(index);
|
||||||
/// )
|
|
||||||
/// .route(web::post().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct JsonConfig {
|
pub struct JsonConfig {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
@ -282,13 +236,13 @@ pub struct JsonConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonConfig {
|
impl JsonConfig {
|
||||||
/// Change max size of payload. By default max size is 32Kb
|
/// Set maximum accepted payload size. By default this limit is 32kB.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.limit = limit;
|
self.limit = limit;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set custom error handler
|
/// Set custom error handler.
|
||||||
pub fn error_handler<F>(mut self, f: F) -> Self
|
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
|
@ -297,7 +251,7 @@ impl JsonConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set predicate for allowed content types
|
/// Set predicate for allowed content types.
|
||||||
pub fn content_type<F>(mut self, predicate: F) -> Self
|
pub fn content_type<F>(mut self, predicate: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
|
F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
|
||||||
|
@ -328,15 +282,14 @@ impl Default for JsonConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's payload json parser, it resolves to a deserialized `T` value.
|
/// Future that resolves to some `T` when parsed from a JSON payload.
|
||||||
/// This future could be used with `ServiceRequest` and `ServiceFromRequest`.
|
|
||||||
///
|
///
|
||||||
/// Returns error:
|
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
|
||||||
///
|
///
|
||||||
/// * content type is not `application/json`
|
/// Returns error if:
|
||||||
/// (unless specified in [`JsonConfig`])
|
/// - content type is not `application/json`
|
||||||
/// * content length is greater than 256k
|
/// - content length is greater than [limit](JsonBody::limit())
|
||||||
pub enum JsonBody<U> {
|
pub enum JsonBody<T> {
|
||||||
Error(Option<JsonPayloadError>),
|
Error(Option<JsonPayloadError>),
|
||||||
Body {
|
Body {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
@ -346,17 +299,17 @@ pub enum JsonBody<U> {
|
||||||
#[cfg(not(feature = "compress"))]
|
#[cfg(not(feature = "compress"))]
|
||||||
payload: Payload,
|
payload: Payload,
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
_res: PhantomData<U>,
|
_res: PhantomData<T>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U> Unpin for JsonBody<U> {}
|
impl<T> Unpin for JsonBody<T> {}
|
||||||
|
|
||||||
impl<U> JsonBody<U>
|
impl<T> JsonBody<T>
|
||||||
where
|
where
|
||||||
U: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
/// Create `JsonBody` for request.
|
/// Create a new future to decode a JSON request payload.
|
||||||
#[allow(clippy::borrow_interior_mutable_const)]
|
#[allow(clippy::borrow_interior_mutable_const)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
|
@ -400,7 +353,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||||
pub fn limit(self, limit: usize) -> Self {
|
pub fn limit(self, limit: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
JsonBody::Body {
|
JsonBody::Body {
|
||||||
|
@ -428,11 +381,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U> Future for JsonBody<U>
|
impl<T> Future for JsonBody<T>
|
||||||
where
|
where
|
||||||
U: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<U, JsonPayloadError>;
|
type Output = Result<T, JsonPayloadError>;
|
||||||
|
|
||||||
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.get_mut();
|
let this = self.get_mut();
|
||||||
|
@ -455,7 +408,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let json = serde_json::from_slice::<U>(&buf)?;
|
let json = serde_json::from_slice::<T>(&buf)?;
|
||||||
return Poll::Ready(Ok(json));
|
return Poll::Ready(Ok(json));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,13 +421,17 @@ where
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::InternalError;
|
use crate::{
|
||||||
use crate::http::header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
error::InternalError,
|
||||||
use crate::test::{load_stream, TestRequest};
|
http::{
|
||||||
use crate::HttpResponse;
|
header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
test::{load_stream, TestRequest},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
struct MyObject {
|
struct MyObject {
|
||||||
|
@ -498,7 +455,7 @@ mod tests {
|
||||||
let j = Json(MyObject {
|
let j = Json(MyObject {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
});
|
});
|
||||||
let resp = j.respond_to(&req).await.unwrap();
|
let resp = j.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
@ -532,7 +489,7 @@ mod tests {
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||||
let mut resp = Response::from_error(s.err().unwrap());
|
let mut resp = HttpResponse::from_error(s.err().unwrap());
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let body = load_stream(resp.take_body()).await.unwrap();
|
let body = load_stream(resp.take_body()).await.unwrap();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Helper types
|
//! Common extractors and responders.
|
||||||
|
|
||||||
|
// TODO: review visibility
|
||||||
mod either;
|
mod either;
|
||||||
pub(crate) mod form;
|
pub(crate) mod form;
|
||||||
pub(crate) mod json;
|
pub(crate) mod json;
|
||||||
|
|
|
@ -1,70 +1,55 @@
|
||||||
//! Path extractor
|
//! For path segment extractor documentation, see [`Path`].
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{fmt, ops};
|
use std::{fmt, ops, sync::Arc};
|
||||||
|
|
||||||
use actix_http::error::{Error, ErrorNotFound};
|
use actix_http::error::{Error, ErrorNotFound};
|
||||||
use actix_router::PathDeserializer;
|
use actix_router::PathDeserializer;
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::{ready, Ready};
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
use crate::dev::Payload;
|
use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest};
|
||||||
use crate::error::PathError;
|
|
||||||
use crate::request::HttpRequest;
|
|
||||||
use crate::FromRequest;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
/// Extract typed data from request path segments.
|
||||||
/// Extract typed information from the request's path.
|
|
||||||
///
|
///
|
||||||
/// [**PathConfig**](PathConfig) allows to configure extraction process.
|
/// Use [`PathConfig`] to configure extraction process.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Usage
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{get, web};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// // extract path info from "/{name}/{count}/index.html" into tuple
|
||||||
/// use actix_web::{web, App};
|
/// // {name} - deserialize a String
|
||||||
///
|
/// // {count} - deserialize a u32
|
||||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
/// #[get("/")]
|
||||||
/// /// {username} - deserializes to a String
|
/// async fn index(path: web::Path<(String, u32)>) -> String {
|
||||||
/// /// {count} - - deserializes to a u32
|
/// let (name, count) = path.into_inner();
|
||||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
/// format!("Welcome {}! {}", name, count)
|
||||||
/// format!("Welcome {}! {}", username, count)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
|
|
||||||
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
|
|
||||||
/// );
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// It is possible to extract path information to a specific type that
|
/// Path segments also can be deserialized into any type that implements [`serde::Deserialize`].
|
||||||
/// implements `Deserialize` trait from *serde*.
|
/// Path segment labels will be matched with struct field names.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_web::{web, App, Error};
|
/// use actix_web::{get, web};
|
||||||
/// use serde_derive::Deserialize;
|
/// use serde::Deserialize;
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
/// username: String,
|
/// name: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// extract `Info` from a path using serde
|
/// // extract `Info` from a path using serde
|
||||||
/// async fn index(info: web::Path<Info>) -> Result<String, Error> {
|
/// #[get("/")]
|
||||||
/// Ok(format!("Welcome {}!", info.username))
|
/// async fn index(info: web::Path<Info>) -> String {
|
||||||
/// }
|
/// format!("Welcome {}!", info.name)
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
|
||||||
/// .route(web::get().to(index)) // <- use handler with Path` extractor
|
|
||||||
/// );
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Path<T>(pub T);
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Path<T>(T);
|
||||||
|
|
||||||
impl<T> Path<T> {
|
impl<T> Path<T> {
|
||||||
/// Deconstruct to an inner value
|
/// Unwrap into inner `T` value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -108,52 +93,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract typed information from the request's path.
|
/// See [here](#usage) for example of usage as an extractor.
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::{web, App};
|
|
||||||
///
|
|
||||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
|
||||||
/// /// {username} - deserializes to a String
|
|
||||||
/// /// {count} - - deserializes to a u32
|
|
||||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
|
||||||
/// format!("Welcome {}! {}", username, count)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
|
|
||||||
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// It is possible to extract path information to a specific type that
|
|
||||||
/// implements `Deserialize` trait from *serde*.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::{web, App, Error};
|
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
|
||||||
/// #[derive(Deserialize)]
|
|
||||||
/// struct Info {
|
|
||||||
/// username: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// /// extract `Info` from a path using serde
|
|
||||||
/// async fn index(info: web::Path<Info>) -> Result<String, Error> {
|
|
||||||
/// Ok(format!("Welcome {}!", info.username))
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
|
||||||
/// .route(web::get().to(index)) // <- use handler with Path` extractor
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl<T> FromRequest for Path<T>
|
impl<T> FromRequest for Path<T>
|
||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
|
@ -191,10 +131,10 @@ where
|
||||||
|
|
||||||
/// Path extractor configuration
|
/// Path extractor configuration
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_web::web::PathConfig;
|
/// use actix_web::web::PathConfig;
|
||||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||||
/// use serde_derive::Deserialize;
|
/// use serde::Deserialize;
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize, Debug)]
|
/// #[derive(Deserialize, Debug)]
|
||||||
/// enum Folder {
|
/// enum Folder {
|
||||||
|
@ -249,7 +189,7 @@ impl Default for PathConfig {
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_router::ResourceDef;
|
use actix_router::ResourceDef;
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde_derive::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
|
|
|
@ -1,57 +1,51 @@
|
||||||
//! Payload/Bytes/String extractors
|
//! Basic binary and string payload extractors.
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::error::{Error, ErrorBadRequest, PayloadError};
|
use std::{
|
||||||
use actix_http::HttpMessage;
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
str,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_http::error::{ErrorBadRequest, PayloadError};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use encoding_rs::{Encoding, UTF_8};
|
use encoding_rs::{Encoding, UTF_8};
|
||||||
use futures_core::stream::Stream;
|
use futures_core::stream::Stream;
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _},
|
future::{ready, Either, ErrInto, Ready, TryFutureExt as _},
|
||||||
ready,
|
ready,
|
||||||
};
|
};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
use crate::extract::FromRequest;
|
use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest};
|
||||||
use crate::http::header;
|
|
||||||
use crate::request::HttpRequest;
|
|
||||||
use crate::{dev, web};
|
|
||||||
|
|
||||||
/// Payload extractor returns request 's payload stream.
|
/// Extract a request's raw payload stream.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// See [`PayloadConfig`] for important notes when using this advanced extractor.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// # Usage
|
||||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
/// ```
|
||||||
/// use std::future::Future;
|
/// use std::future::Future;
|
||||||
/// use futures_core::stream::Stream;
|
/// use futures_util::stream::{Stream, StreamExt};
|
||||||
/// use futures_util::StreamExt;
|
/// use actix_web::{post, web};
|
||||||
/// /// extract binary data from request
|
///
|
||||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
/// // `body: web::Payload` parameter extracts raw payload stream from request
|
||||||
/// {
|
/// #[post("/")]
|
||||||
|
/// async fn index(mut body: web::Payload) -> actix_web::Result<String> {
|
||||||
|
/// // for demonstration only; in a normal case use the `Bytes` extractor
|
||||||
|
/// // collect payload stream into a bytes object
|
||||||
/// let mut bytes = web::BytesMut::new();
|
/// let mut bytes = web::BytesMut::new();
|
||||||
/// while let Some(item) = body.next().await {
|
/// while let Some(item) = body.next().await {
|
||||||
/// bytes.extend_from_slice(&item?);
|
/// bytes.extend_from_slice(&item?);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// format!("Body {:?}!", bytes);
|
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
||||||
/// Ok(HttpResponse::Ok().finish())
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(
|
|
||||||
/// web::get().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Payload(pub crate::dev::Payload);
|
pub struct Payload(pub crate::dev::Payload);
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
/// Deconstruct to a inner value
|
/// Unwrap to inner Payload type.
|
||||||
pub fn into_inner(self) -> crate::dev::Payload {
|
pub fn into_inner(self) -> crate::dev::Payload {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -69,35 +63,7 @@ impl Stream for Payload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get request's payload stream
|
/// See [here](#usage) for example of usage as an extractor.
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
|
||||||
/// use std::future::Future;
|
|
||||||
/// use futures_core::stream::Stream;
|
|
||||||
/// use futures_util::StreamExt;
|
|
||||||
///
|
|
||||||
/// /// extract binary data from request
|
|
||||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
|
||||||
/// {
|
|
||||||
/// let mut bytes = web::BytesMut::new();
|
|
||||||
/// while let Some(item) = body.next().await {
|
|
||||||
/// bytes.extend_from_slice(&item?);
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// format!("Body {:?}!", bytes);
|
|
||||||
/// Ok(HttpResponse::Ok().finish())
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(
|
|
||||||
/// web::get().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl FromRequest for Payload {
|
impl FromRequest for Payload {
|
||||||
type Config = PayloadConfig;
|
type Config = PayloadConfig;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
@ -105,34 +71,25 @@ impl FromRequest for Payload {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||||
ok(Payload(payload.take()))
|
ready(Ok(Payload(payload.take())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request binary data from a request's payload.
|
/// Extract binary data from a request's payload.
|
||||||
///
|
///
|
||||||
/// Loads request's payload and construct Bytes instance.
|
/// Collects request payload stream into a [Bytes] instance.
|
||||||
///
|
///
|
||||||
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
/// Use [`PayloadConfig`] to configure extraction process.
|
||||||
/// extraction process.
|
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Usage
|
||||||
///
|
/// ```
|
||||||
/// ```rust
|
/// use actix_web::{post, web};
|
||||||
/// use bytes::Bytes;
|
|
||||||
/// use actix_web::{web, App};
|
|
||||||
///
|
///
|
||||||
/// /// extract binary data from request
|
/// /// extract binary data from request
|
||||||
/// async fn index(body: Bytes) -> String {
|
/// #[post("/")]
|
||||||
|
/// async fn index(body: web::Bytes) -> String {
|
||||||
/// format!("Body {:?}!", body)
|
/// format!("Body {:?}!", body)
|
||||||
/// }
|
/// }
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(
|
|
||||||
/// web::get().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
impl FromRequest for Bytes {
|
impl FromRequest for Bytes {
|
||||||
type Config = PayloadConfig;
|
type Config = PayloadConfig;
|
||||||
|
@ -144,8 +101,8 @@ impl FromRequest for Bytes {
|
||||||
// allow both Config and Data<Config>
|
// allow both Config and Data<Config>
|
||||||
let cfg = PayloadConfig::from_req(req);
|
let cfg = PayloadConfig::from_req(req);
|
||||||
|
|
||||||
if let Err(e) = cfg.check_mimetype(req) {
|
if let Err(err) = cfg.check_mimetype(req) {
|
||||||
return Either::Right(err(e));
|
return Either::Right(ready(Err(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let limit = cfg.limit;
|
let limit = cfg.limit;
|
||||||
|
@ -161,26 +118,15 @@ impl FromRequest for Bytes {
|
||||||
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
||||||
/// extraction process.
|
/// extraction process.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Usage
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{post, web, FromRequest};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// // extract text data from request
|
||||||
/// use actix_web::{web, App, FromRequest};
|
/// #[post("/")]
|
||||||
///
|
|
||||||
/// /// extract text data from request
|
|
||||||
/// async fn index(text: String) -> String {
|
/// async fn index(text: String) -> String {
|
||||||
/// format!("Body {}!", text)
|
/// format!("Body {}!", text)
|
||||||
/// }
|
/// }
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html")
|
|
||||||
/// .app_data(String::configure(|cfg| { // <- limit size of the payload
|
|
||||||
/// cfg.limit(4096)
|
|
||||||
/// }))
|
|
||||||
/// .route(web::get().to(index)) // <- register handler with extractor params
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl FromRequest for String {
|
impl FromRequest for String {
|
||||||
type Config = PayloadConfig;
|
type Config = PayloadConfig;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
@ -191,14 +137,14 @@ impl FromRequest for String {
|
||||||
let cfg = PayloadConfig::from_req(req);
|
let cfg = PayloadConfig::from_req(req);
|
||||||
|
|
||||||
// check content-type
|
// check content-type
|
||||||
if let Err(e) = cfg.check_mimetype(req) {
|
if let Err(err) = cfg.check_mimetype(req) {
|
||||||
return Either::Right(err(e));
|
return Either::Right(ready(Err(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check charset
|
// check charset
|
||||||
let encoding = match req.encoding() {
|
let encoding = match req.encoding() {
|
||||||
Ok(enc) => enc,
|
Ok(enc) => enc,
|
||||||
Err(e) => return Either::Right(err(e.into())),
|
Err(err) => return Either::Right(ready(Err(err.into()))),
|
||||||
};
|
};
|
||||||
let limit = cfg.limit;
|
let limit = cfg.limit;
|
||||||
let body_fut = HttpMessageBody::new(req, payload).limit(limit);
|
let body_fut = HttpMessageBody::new(req, payload).limit(limit);
|
||||||
|
@ -238,11 +184,13 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, E
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for request's payload.
|
/// Configuration for request payloads.
|
||||||
///
|
///
|
||||||
/// Applies to the built-in `Bytes` and `String` extractors. Note that the Payload extractor does
|
/// Applies to the built-in `Bytes` and `String` extractors. Note that the `Payload` extractor does
|
||||||
/// not automatically check conformance with this configuration to allow more flexibility when
|
/// not automatically check conformance with this configuration to allow more flexibility when
|
||||||
/// building extractors on top of `Payload`.
|
/// building extractors on top of `Payload`.
|
||||||
|
///
|
||||||
|
/// By default, the payload size limit is 256kB and there is no mime type condition.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PayloadConfig {
|
pub struct PayloadConfig {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
@ -250,7 +198,7 @@ pub struct PayloadConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PayloadConfig {
|
impl PayloadConfig {
|
||||||
/// Create `PayloadConfig` instance and set max size of payload.
|
/// Create new instance with a size limit and no mime type condition.
|
||||||
pub fn new(limit: usize) -> Self {
|
pub fn new(limit: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
limit,
|
limit,
|
||||||
|
@ -258,14 +206,13 @@ impl PayloadConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.limit = limit;
|
self.limit = limit;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set required mime-type of the request. By default mime type is not
|
/// Set required mime type of the request. By default mime type is not enforced.
|
||||||
/// enforced.
|
|
||||||
pub fn mimetype(mut self, mt: Mime) -> Self {
|
pub fn mimetype(mut self, mt: Mime) -> Self {
|
||||||
self.mimetype = Some(mt);
|
self.mimetype = Some(mt);
|
||||||
self
|
self
|
||||||
|
@ -292,7 +239,7 @@ impl PayloadConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||||
/// back to the default payload config.
|
/// back to the default payload config if neither is found.
|
||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
||||||
|
@ -314,13 +261,10 @@ impl Default for PayloadConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Future that resolves to a complete http message body.
|
/// Future that resolves to a complete HTTP body payload.
|
||||||
///
|
///
|
||||||
/// Load http message body.
|
/// By default only 256kB payload is accepted before `PayloadError::Overflow` is returned.
|
||||||
///
|
/// Use `MessageBody::limit()` method to change upper limit.
|
||||||
/// By default only 256Kb payload reads to a memory, then
|
|
||||||
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
|
|
||||||
/// method to change upper limit.
|
|
||||||
pub struct HttpMessageBody {
|
pub struct HttpMessageBody {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
|
@ -366,7 +310,7 @@ impl HttpMessageBody {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Change max size of payload. By default max size is 256kB
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
if let Some(l) = self.length {
|
if let Some(l) = self.length {
|
||||||
if l > limit {
|
if l > limit {
|
||||||
|
@ -384,8 +328,8 @@ impl Future for HttpMessageBody {
|
||||||
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.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
if let Some(e) = this.err.take() {
|
if let Some(err) = this.err.take() {
|
||||||
return Poll::Ready(Err(e));
|
return Poll::Ready(Err(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
//! Query extractor
|
//! For query parameter extractor documentation, see [`Query`].
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{fmt, ops, sync::Arc};
|
||||||
use std::{fmt, ops};
|
|
||||||
|
|
||||||
use actix_http::error::Error;
|
|
||||||
use futures_util::future::{err, ok, Ready};
|
use futures_util::future::{err, ok, Ready};
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
use crate::dev::Payload;
|
use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
|
||||||
use crate::error::QueryPayloadError;
|
|
||||||
use crate::extract::FromRequest;
|
|
||||||
use crate::request::HttpRequest;
|
|
||||||
|
|
||||||
/// Extract typed information from the request's query.
|
/// Extract typed information from the request's query.
|
||||||
///
|
///
|
||||||
/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot
|
/// To extract typed data from the URL query string, the inner type `T` must implement the
|
||||||
/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs.
|
/// [`serde::Deserialize`] trait.
|
||||||
/// Attempts to do so will *fail at runtime*.
|
|
||||||
///
|
///
|
||||||
/// [**QueryConfig**](QueryConfig) allows to configure extraction process.
|
/// Use [`QueryConfig`] to configure extraction process.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Panics
|
||||||
|
/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
|
||||||
|
/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// # Usage
|
||||||
/// use actix_web::{web, App};
|
/// ```
|
||||||
/// use serde_derive::Deserialize;
|
/// use actix_web::{get, web};
|
||||||
|
/// use serde::Deserialize;
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Deserialize)]
|
/// #[derive(Debug, Deserialize)]
|
||||||
/// pub enum ResponseType {
|
/// pub enum ResponseType {
|
||||||
|
@ -38,35 +35,40 @@ use crate::request::HttpRequest;
|
||||||
/// response_type: ResponseType,
|
/// response_type: ResponseType,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Use `Query` extractor for query information (and destructure it within the signature).
|
/// // Deserialize `AuthRequest` struct from query string.
|
||||||
/// // This handler gets called only if the request's query string contains `id` and `response_type` fields.
|
/// // This handler gets called only if the request's query parameters contain both fields.
|
||||||
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
|
/// // A valid request path for this handler would be `/?id=64&response_type=Code"`.
|
||||||
/// async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
|
/// #[get("/")]
|
||||||
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
/// async fn index(info: web::Query<AuthRequest>) -> String {
|
||||||
/// }
|
/// format!("Authorization request for id={} and type={:?}!", info.id, info.response_type)
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Query<T>(pub T);
|
pub struct Query<T>(T);
|
||||||
|
|
||||||
impl<T> Query<T> {
|
impl<T> Query<T> {
|
||||||
/// Deconstruct to a inner value
|
/// Unwrap into inner `T` value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get query parameters from the path
|
/// Deserialize `T` from a URL encoded query parameter string.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::collections::HashMap;
|
||||||
|
/// # use actix_web::web::Query;
|
||||||
|
/// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
|
||||||
|
/// assert_eq!(numbers.get("one"), Some(&1));
|
||||||
|
/// assert_eq!(numbers.get("two"), Some(&2));
|
||||||
|
/// assert!(numbers.get("three").is_none());
|
||||||
|
/// ```
|
||||||
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
|
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
|
||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
serde_urlencoded::from_str::<T>(query_str)
|
serde_urlencoded::from_str::<T>(query_str)
|
||||||
.map(|val| Ok(Query(val)))
|
.map(Self)
|
||||||
.unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e)))
|
.map_err(QueryPayloadError::Deserialize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,39 +98,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract typed information from the request's query.
|
/// See [here](#usage) for example of usage as an extractor.
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web::{web, App};
|
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
|
||||||
/// #[derive(Debug, Deserialize)]
|
|
||||||
/// pub enum ResponseType {
|
|
||||||
/// Token,
|
|
||||||
/// Code
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(Deserialize)]
|
|
||||||
/// pub struct AuthRequest {
|
|
||||||
/// id: u64,
|
|
||||||
/// response_type: ResponseType,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Use `Query` extractor for query information.
|
|
||||||
/// // This handler get called only if request's query contains `id` and `response_type` fields.
|
|
||||||
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
|
|
||||||
/// async fn index(info: web::Query<AuthRequest>) -> String {
|
|
||||||
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/index.html")
|
|
||||||
/// .route(web::get().to(index))); // <- use `Query` extractor
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl<T> FromRequest for Query<T>
|
impl<T> FromRequest for Query<T>
|
||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
|
@ -141,7 +111,7 @@ where
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
let error_handler = req
|
let error_handler = req
|
||||||
.app_data::<Self::Config>()
|
.app_data::<Self::Config>()
|
||||||
.map(|c| c.ehandler.clone())
|
.map(|c| c.err_handler.clone())
|
||||||
.unwrap_or(None);
|
.unwrap_or(None);
|
||||||
|
|
||||||
serde_urlencoded::from_str::<T>(req.query_string())
|
serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
|
@ -166,13 +136,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query extractor configuration
|
/// Query extractor configuration.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Usage
|
||||||
///
|
/// ```
|
||||||
/// ```rust
|
/// use actix_web::{error, get, web, App, FromRequest, HttpResponse};
|
||||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
/// use serde::Deserialize;
|
||||||
/// use serde_derive::Deserialize;
|
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
|
@ -180,27 +149,25 @@ where
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// deserialize `Info` from request's querystring
|
/// /// deserialize `Info` from request's querystring
|
||||||
|
/// #[get("/")]
|
||||||
/// async fn index(info: web::Query<Info>) -> String {
|
/// async fn index(info: web::Query<Info>) -> String {
|
||||||
/// format!("Welcome {}!", info.username)
|
/// format!("Welcome {}!", info.username)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// // custom `Query` extractor configuration
|
||||||
/// let app = App::new().service(
|
/// let query_cfg = web::QueryConfig::default()
|
||||||
/// web::resource("/index.html").app_data(
|
/// // use custom error handler
|
||||||
/// // change query extractor configuration
|
/// .error_handler(|err, req| {
|
||||||
/// web::QueryConfig::default()
|
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||||
/// .error_handler(|err, req| { // <- create custom error response
|
/// });
|
||||||
/// error::InternalError::from_response(
|
///
|
||||||
/// err, HttpResponse::Conflict().finish()).into()
|
/// App::new()
|
||||||
/// })
|
/// .app_data(query_cfg)
|
||||||
/// )
|
/// .service(index);
|
||||||
/// .route(web::post().to(index))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QueryConfig {
|
pub struct QueryConfig {
|
||||||
ehandler:
|
err_handler:
|
||||||
Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
|
Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,14 +177,14 @@ impl QueryConfig {
|
||||||
where
|
where
|
||||||
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.ehandler = Some(Arc::new(f));
|
self.err_handler = Some(Arc::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QueryConfig {
|
impl Default for QueryConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
QueryConfig { ehandler: None }
|
QueryConfig { err_handler: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +192,7 @@ impl Default for QueryConfig {
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::http::StatusCode;
|
use actix_http::http::StatusCode;
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde_derive::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::InternalError;
|
use crate::error::InternalError;
|
||||||
|
@ -271,6 +238,17 @@ mod tests {
|
||||||
assert_eq!(s.id, "test1");
|
assert_eq!(s.id, "test1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[should_panic]
|
||||||
|
async fn test_tuple_panic() {
|
||||||
|
let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
|
||||||
|
let (req, mut pl) = req.into_parts();
|
||||||
|
|
||||||
|
Query::<(u32, u32)>::from_request(&req, &mut pl)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_custom_error_responder() {
|
async fn test_custom_error_responder() {
|
||||||
let req = TestRequest::with_uri("/name/user1/")
|
let req = TestRequest::with_uri("/name/user1/")
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
use std::borrow::Cow;
|
//! For request line reader documentation, see [`Readlines`].
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str;
|
use std::{
|
||||||
use std::task::{Context, Poll};
|
borrow::Cow,
|
||||||
|
pin::Pin,
|
||||||
|
str,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use encoding_rs::{Encoding, UTF_8};
|
use encoding_rs::{Encoding, UTF_8};
|
||||||
use futures_util::stream::Stream;
|
use futures_core::{ready, stream::Stream};
|
||||||
|
|
||||||
use crate::dev::Payload;
|
use crate::{
|
||||||
use crate::error::{PayloadError, ReadlinesError};
|
dev::Payload,
|
||||||
use crate::HttpMessage;
|
error::{PayloadError, ReadlinesError},
|
||||||
|
HttpMessage,
|
||||||
|
};
|
||||||
|
|
||||||
/// Stream to read request line by line.
|
/// Stream that reads request line by line.
|
||||||
pub struct Readlines<T: HttpMessage> {
|
pub struct Readlines<T: HttpMessage> {
|
||||||
stream: Payload<T::Stream>,
|
stream: Payload<T::Stream>,
|
||||||
buff: BytesMut,
|
buff: BytesMut,
|
||||||
|
@ -43,7 +49,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max line size. By default max size is 256Kb
|
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.limit = limit;
|
self.limit = limit;
|
||||||
self
|
self
|
||||||
|
@ -108,9 +114,10 @@ where
|
||||||
}
|
}
|
||||||
this.checked_buff = true;
|
this.checked_buff = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// poll req for more bytes
|
// poll req for more bytes
|
||||||
match Pin::new(&mut this.stream).poll_next(cx) {
|
match ready!(Pin::new(&mut this.stream).poll_next(cx)) {
|
||||||
Poll::Ready(Some(Ok(mut bytes))) => {
|
Some(Ok(mut bytes)) => {
|
||||||
// check if there is a newline in bytes
|
// check if there is a newline in bytes
|
||||||
let mut found: Option<usize> = None;
|
let mut found: Option<usize> = None;
|
||||||
for (ind, b) in bytes.iter().enumerate() {
|
for (ind, b) in bytes.iter().enumerate() {
|
||||||
|
@ -144,8 +151,8 @@ where
|
||||||
this.buff.extend_from_slice(&bytes);
|
this.buff.extend_from_slice(&bytes);
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
Poll::Ready(None) => {
|
None => {
|
||||||
if this.buff.is_empty() {
|
if this.buff.is_empty() {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
|
@ -165,7 +172,8 @@ where
|
||||||
this.buff.clear();
|
this.buff.clear();
|
||||||
Poll::Ready(Some(Ok(line)))
|
Poll::Ready(Some(Ok(line)))
|
||||||
}
|
}
|
||||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))),
|
|
||||||
|
Some(Err(err)) => Poll::Ready(Some(Err(ReadlinesError::from(err)))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,5 +280,8 @@ where
|
||||||
I: Send + 'static,
|
I: Send + 'static,
|
||||||
E: Send + std::fmt::Debug + 'static,
|
E: Send + std::fmt::Debug + 'static,
|
||||||
{
|
{
|
||||||
actix_threadpool::run(f).await
|
match actix_rt::task::spawn_blocking(f).await {
|
||||||
|
Ok(res) => res.map_err(BlockingError::Error),
|
||||||
|
Err(_) => Err(BlockingError::Canceled),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue