mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-23 15:51:06 +01:00
migrate actix-web to std::future
This commit is contained in:
parent
d081e57316
commit
3127dd4db6
88
Cargo.toml
88
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "1.0.9"
|
||||
version = "2.0.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"]
|
||||
features = ["openssl", "rustls", "brotli", "flate2-zlib", "secure-cookies", "client"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||
@ -28,20 +28,19 @@ path = "src/lib.rs"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
# ".",
|
||||
# "awc",
|
||||
# #"actix-http",
|
||||
# "actix-cors",
|
||||
# "actix-files",
|
||||
# "actix-framed",
|
||||
# "actix-session",
|
||||
# "actix-identity",
|
||||
# "actix-multipart",
|
||||
# "actix-web-actors",
|
||||
# "actix-web-codegen",
|
||||
# "test-server",
|
||||
".",
|
||||
"awc",
|
||||
"actix-http",
|
||||
"actix-cors",
|
||||
"actix-files",
|
||||
"actix-framed",
|
||||
"actix-session",
|
||||
"actix-identity",
|
||||
"actix-multipart",
|
||||
"actix-web-actors",
|
||||
"actix-web-codegen",
|
||||
"test-server",
|
||||
]
|
||||
exclude = ["awc", "actix-http", "test-server"]
|
||||
|
||||
[features]
|
||||
default = ["brotli", "flate2-zlib", "client", "fail"]
|
||||
@ -64,37 +63,35 @@ secure-cookies = ["actix-http/secure-cookies"]
|
||||
fail = ["actix-http/fail"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
|
||||
openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"]
|
||||
|
||||
# unix domain sockets support
|
||||
uds = ["actix-server/uds"]
|
||||
rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.4.1"
|
||||
actix-utils = "0.4.4"
|
||||
actix-codec = "0.2.0-alpha.1"
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
actix-utils = "0.5.0-alpha.1"
|
||||
actix-router = "0.1.5"
|
||||
actix-rt = "0.2.4"
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
actix-web-codegen = "0.1.2"
|
||||
actix-http = "0.2.11"
|
||||
actix-server = "0.6.1"
|
||||
actix-server-config = "0.1.2"
|
||||
actix-testing = "0.1.0"
|
||||
actix-threadpool = "0.1.1"
|
||||
awc = { version = "0.2.7", optional = true }
|
||||
actix-http = "0.3.0-alpha.1"
|
||||
actix-server = "0.8.0-alpha.1"
|
||||
actix-server-config = "0.3.0-alpha.1"
|
||||
actix-testing = "0.3.0-alpha.1"
|
||||
actix-threadpool = "0.2.0-alpha.1"
|
||||
awc = { version = "0.3.0-alpha.1", optional = true }
|
||||
|
||||
bytes = "0.4"
|
||||
derive_more = "0.15.0"
|
||||
encoding_rs = "0.8"
|
||||
futures = "0.1.25"
|
||||
futures = "0.3.1"
|
||||
hashbrown = "0.6.3"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
net2 = "0.2.33"
|
||||
parking_lot = "0.9"
|
||||
pin-project = "0.4.5"
|
||||
regex = "1.0"
|
||||
serde = { version = "1.0", features=["derive"] }
|
||||
serde_json = "1.0"
|
||||
@ -103,17 +100,17 @@ time = "0.1.42"
|
||||
url = "2.1"
|
||||
|
||||
# ssl support
|
||||
openssl = { version="0.10", optional = true }
|
||||
rustls = { version = "0.15", optional = true }
|
||||
open-ssl = { version="0.10", package="openssl", optional = true }
|
||||
rust-tls = { version = "0.16", package="rustls", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix = "0.8.3"
|
||||
actix-connect = "0.2.2"
|
||||
actix-http-test = "0.2.4"
|
||||
# actix = "0.8.3"
|
||||
actix-connect = "0.3.0-alpha.1"
|
||||
actix-http-test = "0.3.0-alpha.1"
|
||||
rand = "0.7"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
tokio-timer = "0.2.8"
|
||||
tokio-timer = "0.3.0-alpha.6"
|
||||
brotli2 = "0.3.2"
|
||||
flate2 = "1.0.2"
|
||||
|
||||
@ -123,19 +120,18 @@ opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
# actix-web = { path = "." }
|
||||
# actix-http = { path = "actix-http" }
|
||||
# actix-http-test = { path = "test-server" }
|
||||
# actix-web-codegen = { path = "actix-web-codegen" }
|
||||
actix-web = { path = "." }
|
||||
actix-http = { path = "actix-http" }
|
||||
actix-http-test = { path = "test-server" }
|
||||
actix-web-codegen = { path = "actix-web-codegen" }
|
||||
# actix-web-actors = { path = "actix-web-actors" }
|
||||
# actix-session = { path = "actix-session" }
|
||||
# actix-files = { path = "actix-files" }
|
||||
# actix-multipart = { path = "actix-multipart" }
|
||||
# awc = { path = "awc" }
|
||||
actix-session = { path = "actix-session" }
|
||||
actix-files = { path = "actix-files" }
|
||||
actix-multipart = { path = "actix-multipart" }
|
||||
awc = { path = "awc" }
|
||||
|
||||
actix-codec = { path = "../actix-net/actix-codec" }
|
||||
actix-connect = { path = "../actix-net/actix-connect" }
|
||||
actix-ioframe = { path = "../actix-net/actix-ioframe" }
|
||||
actix-rt = { path = "../actix-net/actix-rt" }
|
||||
actix-server = { path = "../actix-net/actix-server" }
|
||||
actix-server-config = { path = "../actix-net/actix-server-config" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-cors"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Cross-origin resource sharing (CORS) for Actix applications."
|
||||
readme = "README.md"
|
||||
@ -10,14 +10,14 @@ repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-cors/"
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2018"
|
||||
#workspace = ".."
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_cors"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0"
|
||||
actix-service = "0.4.0"
|
||||
actix-web = "2.0.0-alpha.1"
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
derive_more = "0.15.0"
|
||||
futures = "0.1.25"
|
||||
futures = "0.3.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.1.7"
|
||||
version = "0.2.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static files support for actix web."
|
||||
readme = "README.md"
|
||||
@ -18,12 +18,12 @@ name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "1.0.8", default-features = false }
|
||||
actix-http = "0.2.11"
|
||||
actix-service = "0.4.1"
|
||||
actix-web = { version = "2.0.0-alpha.1", default-features = false }
|
||||
actix-http = "0.3.0-alpha.1"
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
bitflags = "1"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
futures = "0.3.1"
|
||||
derive_more = "0.15.0"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
@ -32,4 +32,4 @@ percent-encoding = "2.1"
|
||||
v_htmlescape = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "1.0.8", features=["ssl"] }
|
||||
actix-web = { version = "2.0.0-alpha.1", features=["openssl"] }
|
||||
|
@ -303,9 +303,8 @@ impl Files {
|
||||
/// Set custom directory renderer
|
||||
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
|
||||
where
|
||||
for<'r, 's> F:
|
||||
Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
|
||||
+ 'static,
|
||||
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
|
||||
+ 'static,
|
||||
{
|
||||
self.renderer = Rc::new(f);
|
||||
self
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-framed"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix framed app server"
|
||||
readme = "README.md"
|
||||
@ -20,19 +20,19 @@ name = "actix_framed"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.4.1"
|
||||
actix-codec = "0.2.0-alpha.1"
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
actix-router = "0.1.2"
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.2.7"
|
||||
actix-server-config = "0.1.2"
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
actix-http = "0.3.0-alpha.1"
|
||||
actix-server-config = "0.2.0-alpha.1"
|
||||
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-server = { version = "0.6.0", features=["ssl"] }
|
||||
actix-connect = { version = "0.2.0", features=["ssl"] }
|
||||
actix-http-test = { version = "0.2.4", features=["ssl"] }
|
||||
actix-utils = "0.4.4"
|
||||
actix-server = { version = "0.8.0-alpha.1", features=["openssl"] }
|
||||
actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] }
|
||||
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
|
||||
actix-utils = "0.5.0-alpha.1"
|
||||
|
@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
# workspace = ".."
|
||||
workspace = ".."
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["openssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
|
||||
@ -114,18 +113,3 @@ actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
open-ssl = { version="0.10", package="openssl" }
|
||||
|
||||
[patch.crates-io]
|
||||
awc = { path = "../awc" }
|
||||
actix-http = { path = "." }
|
||||
actix-http-test = { path = "../test-server" }
|
||||
|
||||
actix-codec = { path = "../../actix-net/actix-codec" }
|
||||
actix-connect = { path = "../../actix-net/actix-connect" }
|
||||
actix-rt = { path = "../../actix-net/actix-rt" }
|
||||
actix-server = { path = "../../actix-net/actix-server" }
|
||||
actix-server-config = { path = "../../actix-net/actix-server-config" }
|
||||
actix-service = { path = "../../actix-net/actix-service" }
|
||||
actix-testing = { path = "../../actix-net/actix-testing" }
|
||||
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
|
||||
actix-utils = { path = "../../actix-net/actix-utils" }
|
||||
|
@ -172,12 +172,11 @@ where
|
||||
/// Finish service configuration and create *http service* for HTTP/1 protocol.
|
||||
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
B: MessageBody,
|
||||
F: IntoServiceFactory<S>,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
self.keep_alive,
|
||||
|
@ -51,10 +51,10 @@ impl<T, S, B> Dispatcher<T, S, B>
|
||||
where
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
S::Error: Into<Error>,
|
||||
// S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
service: CloneableService<S>,
|
||||
@ -176,9 +176,9 @@ enum ServiceResponseState<F, B> {
|
||||
impl<F, I, E, B> ServiceResponse<F, I, E, B>
|
||||
where
|
||||
F: Future<Output = Result<I, E>>,
|
||||
E: Into<Error> + 'static,
|
||||
I: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
E: Into<Error>,
|
||||
I: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn prepare_response(
|
||||
&self,
|
||||
@ -244,9 +244,9 @@ where
|
||||
impl<F, I, E, B> Future for ServiceResponse<F, I, E, B>
|
||||
where
|
||||
F: Future<Output = Result<I, E>>,
|
||||
E: Into<Error> + 'static,
|
||||
I: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
E: Into<Error>,
|
||||
I: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-identity"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Identity service for actix web framework."
|
||||
readme = "README.md"
|
||||
@ -17,14 +17,14 @@ name = "actix_identity"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "1.0.0", default-features = false, features = ["secure-cookies"] }
|
||||
actix-service = "0.4.0"
|
||||
futures = "0.1.25"
|
||||
actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] }
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
futures = "0.3.1"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1.42"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.2.3"
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
actix-http = "0.3.0-alpha.1"
|
||||
bytes = "0.4"
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart support for actix web framework."
|
||||
readme = "README.md"
|
||||
@ -18,17 +18,17 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "1.0.0", default-features = false }
|
||||
actix-service = "0.4.1"
|
||||
actix-web = { version = "2.0.0-alpha.1", default-features = false }
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.15.0"
|
||||
httparse = "1.3"
|
||||
futures = "0.1.25"
|
||||
futures = "0.3.1"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
time = "0.1"
|
||||
twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.2.4"
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
actix-http = "0.3.0-alpha.1"
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-session"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Session for actix web framework."
|
||||
readme = "README.md"
|
||||
@ -24,15 +24,15 @@ default = ["cookie-session"]
|
||||
cookie-session = ["actix-web/secure-cookies"]
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0"
|
||||
actix-service = "0.4.1"
|
||||
actix-web = "2.0.0-alpha.1"
|
||||
actix-service = "1.0.0-alpha.1"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.15.0"
|
||||
futures = "0.1.25"
|
||||
hashbrown = "0.5.0"
|
||||
futures = "0.3.1"
|
||||
hashbrown = "0.6.3"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1.42"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
|
@ -63,7 +63,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0-alpha.1"
|
||||
#actix-web = { version = "1.0.8", features=["ssl"] }
|
||||
actix-web = { version = "2.0.0-alpha.1", features=["openssl"] }
|
||||
actix-http = { version = "0.3.0-alpha.1", features=["openssl"] }
|
||||
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
|
||||
actix-utils = "0.5.0-alpha.1"
|
||||
@ -75,17 +75,3 @@ rand = "0.7"
|
||||
tokio-tcp = "0.1"
|
||||
webpki = { version = "0.21" }
|
||||
rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] }
|
||||
|
||||
[patch.crates-io]
|
||||
awc = { path = "." }
|
||||
actix-http = { path = "../actix-http" }
|
||||
actix-http-test = { path = "../test-server" }
|
||||
|
||||
actix-codec = { path = "../../actix-net/actix-codec" }
|
||||
actix-connect = { path = "../../actix-net/actix-connect" }
|
||||
actix-rt = { path = "../../actix-net/actix-rt" }
|
||||
actix-server = { path = "../../actix-net/actix-server" }
|
||||
actix-server-config = { path = "../../actix-net/actix-server-config" }
|
||||
actix-service = { path = "../../actix-net/actix-service" }
|
||||
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
|
||||
actix-utils = { path = "../../actix-net/actix-utils" }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
||||
use futures::IntoFuture;
|
||||
|
||||
use actix_web::{
|
||||
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
|
||||
};
|
||||
@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path<String>) -> String {
|
||||
format!("Hello: {}!\r\n", name)
|
||||
}
|
||||
|
||||
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
|
||||
async fn index_async(req: HttpRequest) -> Result<&'static str, Error> {
|
||||
println!("REQ: {:?}", req);
|
||||
Ok("Hello world!\r\n")
|
||||
}
|
||||
@ -28,7 +26,7 @@ fn main() -> std::io::Result<()> {
|
||||
App::new()
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
// .wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(no_params)
|
||||
.service(
|
||||
|
@ -1,26 +1,27 @@
|
||||
use actix_http::Error;
|
||||
use actix_rt::System;
|
||||
use futures::{future::lazy, Future};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
std::env::set_var("RUST_LOG", "actix_http=trace");
|
||||
env_logger::init();
|
||||
|
||||
System::new("test").block_on(lazy(|| {
|
||||
awc::Client::new()
|
||||
.get("https://www.rust-lang.org/") // <- Create request builder
|
||||
.header("User-Agent", "Actix-web")
|
||||
.send() // <- Send http request
|
||||
.from_err()
|
||||
.and_then(|mut response| {
|
||||
// <- server http response
|
||||
println!("Response: {:?}", response);
|
||||
System::new("test").block_on(async {
|
||||
let client = awc::Client::new();
|
||||
|
||||
// read response body
|
||||
response
|
||||
.body()
|
||||
.from_err()
|
||||
.map(|body| println!("Downloaded: {:?} bytes", body.len()))
|
||||
})
|
||||
}))
|
||||
// Create request builder, configure request and send
|
||||
let mut response = client
|
||||
.get("https://www.rust-lang.org/")
|
||||
.header("User-Agent", "Actix-web")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// server http response
|
||||
println!("Response: {:?}", response);
|
||||
|
||||
// read response body
|
||||
let body = response.body().await?;
|
||||
println!("Downloaded: {:?} bytes", body.len());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
use futures::IntoFuture;
|
||||
|
||||
use actix_web::{
|
||||
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
|
||||
};
|
||||
@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path<String>) -> String {
|
||||
format!("Hello: {}!\r\n", name)
|
||||
}
|
||||
|
||||
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
|
||||
async fn index_async(req: HttpRequest) -> Result<&'static str, Error> {
|
||||
println!("REQ: {:?}", req);
|
||||
Ok("Hello world!\r\n")
|
||||
}
|
||||
@ -29,7 +27,7 @@ fn main() -> std::io::Result<()> {
|
||||
App::new()
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
// .wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(no_params)
|
||||
.service(
|
||||
@ -38,7 +36,7 @@ fn main() -> std::io::Result<()> {
|
||||
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
|
||||
)
|
||||
.default_service(
|
||||
web::route().to(|| HttpResponse::MethodNotAllowed()),
|
||||
web::route().to(|| ok(HttpResponse::MethodNotAllowed())),
|
||||
)
|
||||
.route(web::get().to_async(index_async)),
|
||||
)
|
||||
|
414
src/app.rs
414
src/app.rs
@ -1,14 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::body::{Body, MessageBody};
|
||||
use actix_service::boxed::{self, BoxedNewService};
|
||||
use actix_service::{
|
||||
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
|
||||
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform,
|
||||
};
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::future::{FutureExt, LocalBoxFuture};
|
||||
|
||||
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
||||
use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
|
||||
@ -18,19 +21,19 @@ use crate::error::Error;
|
||||
use crate::resource::Resource;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
type FnDataFactory =
|
||||
Box<dyn Fn() -> Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>;
|
||||
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<dyn ServiceFactory>>,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
data: Vec<Box<dyn DataFactory>>,
|
||||
@ -61,7 +64,7 @@ impl App<AppEntry, Body> {
|
||||
impl<T, B> App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -107,24 +110,30 @@ where
|
||||
/// Set application data factory. This function is
|
||||
/// similar to `.data()` but it accepts data factory. Data object get
|
||||
/// constructed asynchronously during application initialization.
|
||||
pub fn data_factory<F, Out>(mut self, data: F) -> Self
|
||||
pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self
|
||||
where
|
||||
F: Fn() -> Out + 'static,
|
||||
Out: IntoFuture + 'static,
|
||||
Out::Error: std::fmt::Debug,
|
||||
Out: Future<Output = Result<D, E>> + 'static,
|
||||
D: 'static,
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
self.data_factories.push(Box::new(move || {
|
||||
Box::new(
|
||||
data()
|
||||
.into_future()
|
||||
.map_err(|e| {
|
||||
log::error!("Can not construct data instance: {:?}", e);
|
||||
})
|
||||
.map(|data| {
|
||||
let data: Box<dyn DataFactory> = Box::new(Data::new(data));
|
||||
data
|
||||
}),
|
||||
)
|
||||
{
|
||||
let fut = data();
|
||||
async move {
|
||||
match fut.await {
|
||||
Err(e) => {
|
||||
log::error!("Can not construct data instance: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
Ok(data) => {
|
||||
let data: Box<dyn DataFactory> = Box::new(Data::new(data));
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed_local()
|
||||
}));
|
||||
self
|
||||
}
|
||||
@ -267,8 +276,8 @@ where
|
||||
/// ```
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
F: IntoServiceFactory<U>,
|
||||
U: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -277,11 +286,9 @@ where
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(boxed::new_service(
|
||||
f.into_new_service().map_init_err(|e| {
|
||||
log::error!("Can not construct default service: {:?}", e)
|
||||
}),
|
||||
)));
|
||||
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
||||
|e| log::error!("Can not construct default service: {:?}", e),
|
||||
))));
|
||||
|
||||
self
|
||||
}
|
||||
@ -350,11 +357,11 @@ where
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap<M, B1, F>(
|
||||
pub fn wrap<M, B1>(
|
||||
self,
|
||||
mw: F,
|
||||
mw: M,
|
||||
) -> App<
|
||||
impl NewService<
|
||||
impl ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B1>,
|
||||
@ -372,11 +379,9 @@ where
|
||||
InitError = (),
|
||||
>,
|
||||
B1: MessageBody,
|
||||
F: IntoTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = apply_transform(mw, self.endpoint);
|
||||
App {
|
||||
endpoint,
|
||||
endpoint: apply(mw, self.endpoint),
|
||||
data: self.data,
|
||||
data_factories: self.data_factories,
|
||||
services: self.services,
|
||||
@ -407,13 +412,16 @@ where
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap_fn(|req, srv|
|
||||
/// srv.call(req).map(|mut res| {
|
||||
/// .wrap_fn(|req, srv| {
|
||||
/// let fut = srv.call(req);
|
||||
/// async {
|
||||
/// let mut res = fut.await?;
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// res
|
||||
/// }))
|
||||
/// Ok(res)
|
||||
/// }
|
||||
/// })
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
@ -421,7 +429,7 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> App<
|
||||
impl NewService<
|
||||
impl ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B1>,
|
||||
@ -433,16 +441,26 @@ where
|
||||
where
|
||||
B1: MessageBody,
|
||||
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
|
||||
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
App {
|
||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||
data: self.data,
|
||||
data_factories: self.data_factories,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
factory_ref: self.factory_ref,
|
||||
config: self.config,
|
||||
external: self.external,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> IntoNewService<AppInit<T, B>> for App<T, B>
|
||||
impl<T, B> IntoServiceFactory<AppInit<T, B>> for App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -450,7 +468,7 @@ where
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
fn into_new_service(self) -> AppInit<T, B> {
|
||||
fn into_factory(self) -> AppInit<T, B> {
|
||||
AppInit {
|
||||
data: Rc::new(self.data),
|
||||
data_factories: Rc::new(self.data_factories),
|
||||
@ -468,82 +486,89 @@ where
|
||||
mod tests {
|
||||
use actix_service::Service;
|
||||
use bytes::Bytes;
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::future::{ok, Future};
|
||||
|
||||
use super::*;
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::middleware::DefaultHeaders;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::test::{
|
||||
block_fn, block_on, call_service, init_service, read_body, TestRequest,
|
||||
};
|
||||
use crate::test::{block_on, call_service, init_service, read_body, TestRequest};
|
||||
use crate::{web, Error, HttpRequest, HttpResponse};
|
||||
|
||||
#[test]
|
||||
fn test_default_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = block_fn(|| srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/blah").to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
let req = TestRequest::with_uri("/blah").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
.service(
|
||||
web::resource("/test2")
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::Created())
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::MethodNotAllowed())
|
||||
}),
|
||||
);
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
.service(
|
||||
web::resource("/test2")
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::Created()))
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::MethodNotAllowed()))
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/blah").to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
let req = TestRequest::with_uri("/blah").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let req = TestRequest::with_uri("/test2").to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/test2").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test2")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
let req = TestRequest::with_uri("/test2")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_factory() {
|
||||
let mut srv =
|
||||
init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
block_on(async {
|
||||
let mut srv =
|
||||
init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
let mut srv =
|
||||
init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
}
|
||||
|
||||
fn md<S, B>(
|
||||
req: ServiceRequest,
|
||||
srv: &mut S,
|
||||
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
|
||||
) -> impl Future<Output = Result<ServiceResponse<B>, Error>>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest,
|
||||
@ -551,112 +576,141 @@ mod tests {
|
||||
Error = Error,
|
||||
>,
|
||||
{
|
||||
srv.call(req).map(|mut res| {
|
||||
let fut = srv.call(req);
|
||||
async move {
|
||||
let mut res = fut.await?;
|
||||
res.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
res
|
||||
})
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.wrap(md)
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok())),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv =
|
||||
init_service(
|
||||
App::new()
|
||||
.wrap(DefaultHeaders::new().header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
))
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_router_wrap() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.wrap(md),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv =
|
||||
init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.wrap(DefaultHeaders::new().header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_fn() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
srv.call(req).map(|mut res| {
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
res
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
let fut = srv.call(req);
|
||||
async move {
|
||||
let mut res = fut.await?;
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_router_wrap_fn() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.wrap_fn(|req, srv| {
|
||||
srv.call(req).map(|mut res| {
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
res
|
||||
})
|
||||
}),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.wrap_fn(|req, srv| {
|
||||
let fut = srv.call(req);
|
||||
async {
|
||||
let mut res = fut.await?;
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_external_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
}),
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = read_body(resp);
|
||||
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = read_body(resp).await;
|
||||
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::{Extensions, Request, Response};
|
||||
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||
use actix_service::{service_fn, NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use actix_service::{service_fn, Service, ServiceFactory};
|
||||
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::DataFactory;
|
||||
@ -16,23 +18,20 @@ use crate::error::Error;
|
||||
use crate::guard::Guard;
|
||||
use crate::request::{HttpRequest, HttpRequestPool};
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
|
||||
use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse};
|
||||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
type BoxedResponse = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<dyn Future<Item = ServiceResponse, Error = Error>>,
|
||||
>;
|
||||
type BoxedResponse = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
|
||||
type FnDataFactory =
|
||||
Box<dyn Fn() -> Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>;
|
||||
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||
/// It also executes data factories.
|
||||
pub struct AppInit<T, B>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -44,15 +43,15 @@ where
|
||||
pub(crate) data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
pub(crate) data_factories: Rc<Vec<FnDataFactory>>,
|
||||
pub(crate) config: RefCell<AppConfig>,
|
||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn ServiceFactory>>>>,
|
||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
||||
}
|
||||
|
||||
impl<T, B> NewService for AppInit<T, B>
|
||||
impl<T, B> ServiceFactory for AppInit<T, B>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -71,8 +70,8 @@ where
|
||||
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
|
||||
// update resource default service
|
||||
let default = self.default.clone().unwrap_or_else(|| {
|
||||
Rc::new(boxed::new_service(service_fn(|req: ServiceRequest| {
|
||||
Ok(req.into_response(Response::NotFound().finish()))
|
||||
Rc::new(boxed::factory(service_fn(|req: ServiceRequest| {
|
||||
ok(req.into_response(Response::NotFound().finish()))
|
||||
})))
|
||||
});
|
||||
|
||||
@ -135,23 +134,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct AppInitResult<T, B>
|
||||
where
|
||||
T: NewService,
|
||||
T: ServiceFactory,
|
||||
{
|
||||
endpoint: Option<T::Service>,
|
||||
#[pin]
|
||||
endpoint_fut: T::Future,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
data_factories: Vec<Box<dyn DataFactory>>,
|
||||
data_factories_fut: Vec<Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>,
|
||||
data_factories_fut: Vec<LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>,
|
||||
_t: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<T, B> Future for AppInitResult<T, B>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -159,48 +160,49 @@ where
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
type Item = AppInitService<T::Service, B>;
|
||||
type Error = ();
|
||||
type Output = Result<AppInitService<T::Service, B>, ()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
// async data factories
|
||||
let mut idx = 0;
|
||||
while idx < self.data_factories_fut.len() {
|
||||
match self.data_factories_fut[idx].poll()? {
|
||||
Async::Ready(f) => {
|
||||
self.data_factories.push(f);
|
||||
let _ = self.data_factories_fut.remove(idx);
|
||||
while idx < this.data_factories_fut.len() {
|
||||
match Pin::new(&mut this.data_factories_fut[idx]).poll(cx)? {
|
||||
Poll::Ready(f) => {
|
||||
this.data_factories.push(f);
|
||||
let _ = this.data_factories_fut.remove(idx);
|
||||
}
|
||||
Async::NotReady => idx += 1,
|
||||
Poll::Pending => idx += 1,
|
||||
}
|
||||
}
|
||||
|
||||
if self.endpoint.is_none() {
|
||||
if let Async::Ready(srv) = self.endpoint_fut.poll()? {
|
||||
self.endpoint = Some(srv);
|
||||
if this.endpoint.is_none() {
|
||||
if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? {
|
||||
*this.endpoint = Some(srv);
|
||||
}
|
||||
}
|
||||
|
||||
if self.endpoint.is_some() && self.data_factories_fut.is_empty() {
|
||||
if this.endpoint.is_some() && this.data_factories_fut.is_empty() {
|
||||
// create app data container
|
||||
let mut data = Extensions::new();
|
||||
for f in self.data.iter() {
|
||||
for f in this.data.iter() {
|
||||
f.create(&mut data);
|
||||
}
|
||||
|
||||
for f in &self.data_factories {
|
||||
for f in this.data_factories.iter() {
|
||||
f.create(&mut data);
|
||||
}
|
||||
|
||||
Ok(Async::Ready(AppInitService {
|
||||
service: self.endpoint.take().unwrap(),
|
||||
rmap: self.rmap.clone(),
|
||||
config: self.config.clone(),
|
||||
Poll::Ready(Ok(AppInitService {
|
||||
service: this.endpoint.take().unwrap(),
|
||||
rmap: this.rmap.clone(),
|
||||
config: this.config.clone(),
|
||||
data: Rc::new(data),
|
||||
pool: HttpRequestPool::create(),
|
||||
}))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -226,8 +228,8 @@ where
|
||||
type Error = T::Error;
|
||||
type Future = T::Future;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
@ -270,7 +272,7 @@ pub struct AppRoutingFactory {
|
||||
default: Rc<HttpNewService>,
|
||||
}
|
||||
|
||||
impl NewService for AppRoutingFactory {
|
||||
impl ServiceFactory for AppRoutingFactory {
|
||||
type Config = ();
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
@ -288,7 +290,7 @@ impl NewService for AppRoutingFactory {
|
||||
CreateAppRoutingItem::Future(
|
||||
Some(path.clone()),
|
||||
guards.borrow_mut().take(),
|
||||
service.new_service(&()),
|
||||
service.new_service(&()).boxed_local(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
@ -298,14 +300,14 @@ impl NewService for AppRoutingFactory {
|
||||
}
|
||||
}
|
||||
|
||||
type HttpServiceFut = Box<dyn Future<Item = HttpService, Error = ()>>;
|
||||
type HttpServiceFut = LocalBoxFuture<'static, Result<HttpService, ()>>;
|
||||
|
||||
/// Create app service
|
||||
#[doc(hidden)]
|
||||
pub struct AppRoutingFactoryResponse {
|
||||
fut: Vec<CreateAppRoutingItem>,
|
||||
default: Option<HttpService>,
|
||||
default_fut: Option<Box<dyn Future<Item = HttpService, Error = ()>>>,
|
||||
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
|
||||
}
|
||||
|
||||
enum CreateAppRoutingItem {
|
||||
@ -314,16 +316,15 @@ enum CreateAppRoutingItem {
|
||||
}
|
||||
|
||||
impl Future for AppRoutingFactoryResponse {
|
||||
type Item = AppRouting;
|
||||
type Error = ();
|
||||
type Output = Result<AppRouting, ()>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
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 fut.poll()? {
|
||||
Async::Ready(default) => self.default = Some(default),
|
||||
Async::NotReady => done = false,
|
||||
match Pin::new(fut).poll(cx)? {
|
||||
Poll::Ready(default) => self.default = Some(default),
|
||||
Poll::Pending => done = false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,11 +335,12 @@ impl Future for AppRoutingFactoryResponse {
|
||||
ref mut path,
|
||||
ref mut guards,
|
||||
ref mut fut,
|
||||
) => match fut.poll()? {
|
||||
Async::Ready(service) => {
|
||||
) => match Pin::new(fut).poll(cx) {
|
||||
Poll::Ready(Ok(service)) => {
|
||||
Some((path.take().unwrap(), guards.take(), service))
|
||||
}
|
||||
Async::NotReady => {
|
||||
Poll::Ready(Err(_)) => return Poll::Ready(Err(())),
|
||||
Poll::Pending => {
|
||||
done = false;
|
||||
None
|
||||
}
|
||||
@ -364,13 +366,13 @@ impl Future for AppRoutingFactoryResponse {
|
||||
}
|
||||
router
|
||||
});
|
||||
Ok(Async::Ready(AppRouting {
|
||||
Poll::Ready(Ok(AppRouting {
|
||||
ready: None,
|
||||
router: router.finish(),
|
||||
default: self.default.take(),
|
||||
}))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -387,11 +389,11 @@ impl Service for AppRouting {
|
||||
type Error = Error;
|
||||
type Future = BoxedResponse;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
if self.ready.is_none() {
|
||||
Ok(Async::Ready(()))
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,7 +415,7 @@ impl Service for AppRouting {
|
||||
default.call(req)
|
||||
} else {
|
||||
let req = req.into_parts().0;
|
||||
Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish())))
|
||||
ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,7 +431,7 @@ impl AppEntry {
|
||||
}
|
||||
}
|
||||
|
||||
impl NewService for AppEntry {
|
||||
impl ServiceFactory for AppEntry {
|
||||
type Config = ();
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
@ -464,15 +466,16 @@ mod tests {
|
||||
#[test]
|
||||
fn drop_data() {
|
||||
let data = Arc::new(AtomicBool::new(false));
|
||||
{
|
||||
test::block_on(async {
|
||||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.data(DropData(data.clone()))
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
let req = test::TestRequest::with_uri("/test").to_request();
|
||||
let _ = test::block_on(app.call(req)).unwrap();
|
||||
}
|
||||
let _ = app.call(req).await.unwrap();
|
||||
});
|
||||
assert!(data.load(Ordering::Relaxed));
|
||||
}
|
||||
}
|
||||
|
119
src/config.rs
119
src/config.rs
@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||
|
||||
use actix_http::Extensions;
|
||||
use actix_router::ResourceDef;
|
||||
use actix_service::{boxed, IntoNewService, NewService};
|
||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
|
||||
|
||||
use crate::data::{Data, DataFactory};
|
||||
use crate::error::Error;
|
||||
@ -12,7 +12,7 @@ use crate::resource::Resource;
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
|
||||
@ -102,11 +102,11 @@ impl AppService {
|
||||
&mut self,
|
||||
rdef: ResourceDef,
|
||||
guards: Option<Vec<Box<dyn Guard>>>,
|
||||
service: F,
|
||||
factory: F,
|
||||
nested: Option<Rc<ResourceMap>>,
|
||||
) where
|
||||
F: IntoNewService<S>,
|
||||
S: NewService<
|
||||
F: IntoServiceFactory<S>,
|
||||
S: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -116,7 +116,7 @@ impl AppService {
|
||||
{
|
||||
self.services.push((
|
||||
rdef,
|
||||
boxed::new_service(service.into_new_service()),
|
||||
boxed::factory(factory.into_factory()),
|
||||
guards,
|
||||
nested,
|
||||
));
|
||||
@ -174,7 +174,7 @@ impl Default for AppConfigInner {
|
||||
/// to set of external methods. This could help with
|
||||
/// modularization of big application configuration.
|
||||
pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<Box<dyn ServiceFactory>>,
|
||||
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
||||
pub(crate) data: Vec<Box<dyn DataFactory>>,
|
||||
pub(crate) external: Vec<ResourceDef>,
|
||||
}
|
||||
@ -251,17 +251,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let cfg = |cfg: &mut ServiceConfig| {
|
||||
cfg.data(10usize);
|
||||
};
|
||||
block_on(async {
|
||||
let cfg = |cfg: &mut ServiceConfig| {
|
||||
cfg.data(10usize);
|
||||
};
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().configure(cfg).service(
|
||||
let mut srv = init_service(App::new().configure(cfg).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
}
|
||||
|
||||
// #[test]
|
||||
@ -298,50 +300,57 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_external_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.configure(|cfg| {
|
||||
cfg.external_resource(
|
||||
"youtube",
|
||||
"https://youtube.com/watch/{video_id}",
|
||||
);
|
||||
})
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
}),
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = read_body(resp);
|
||||
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.configure(|cfg| {
|
||||
cfg.external_resource(
|
||||
"youtube",
|
||||
"https://youtube.com/watch/{video_id}",
|
||||
);
|
||||
})
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = read_body(resp).await;
|
||||
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service() {
|
||||
let mut srv = init_service(App::new().configure(|cfg| {
|
||||
cfg.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
|
||||
)
|
||||
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
}));
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().configure(|cfg| {
|
||||
cfg.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Created())),
|
||||
)
|
||||
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
}))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/index.html")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/index.html")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
139
src/data.rs
139
src/data.rs
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use actix_http::error::{Error, ErrorInternalServerError};
|
||||
use actix_http::Extensions;
|
||||
use futures::future::{err, ok, Ready};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::extract::FromRequest;
|
||||
@ -101,19 +102,19 @@ impl<T> Clone for Data<T> {
|
||||
impl<T: 'static> FromRequest for Data<T> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
if let Some(st) = req.get_app_data::<T>() {
|
||||
Ok(st)
|
||||
ok(st)
|
||||
} else {
|
||||
log::debug!(
|
||||
"Failed to construct App-level Data extractor. \
|
||||
Request path: {:?}",
|
||||
req.path()
|
||||
);
|
||||
Err(ErrorInternalServerError(
|
||||
err(ErrorInternalServerError(
|
||||
"App data is not configured, to configure use App::data()",
|
||||
))
|
||||
}
|
||||
@ -142,85 +143,99 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_data_extractor() {
|
||||
let mut srv =
|
||||
init_service(App::new().data(10usize).service(
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().data(10usize).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().data(10u32).service(
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_data_extractor() {
|
||||
let mut srv =
|
||||
init_service(App::new().register_data(Data::new(10usize)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
block_on(async {
|
||||
let mut srv =
|
||||
init_service(App::new().register_data(Data::new(10usize)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().register_data(Data::new(10u32)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
let mut srv =
|
||||
init_service(App::new().register_data(Data::new(10u32)).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_data_extractor() {
|
||||
let mut srv =
|
||||
init_service(App::new().service(web::resource("/").data(10usize).route(
|
||||
web::get().to(|data: web::Data<usize>| {
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
)));
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::resource("/").data(10usize).route(web::get().to(
|
||||
|data: web::Data<usize>| {
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// different type
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.data(10u32)
|
||||
.route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
|
||||
),
|
||||
);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
// different type
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.data(10u32)
|
||||
.route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_data() {
|
||||
let mut srv = init_service(App::new().data(1usize).service(
|
||||
web::resource("/").data(10usize).route(web::get().to(
|
||||
|data: web::Data<usize>| {
|
||||
assert_eq!(*data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
));
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().data(1usize).service(
|
||||
web::resource("/").data(10usize).route(web::get().to(
|
||||
|data: web::Data<usize>| {
|
||||
assert_eq!(*data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
101
src/extract.rs
101
src/extract.rs
@ -1,8 +1,10 @@
|
||||
//! Request extractors
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::error::Error;
|
||||
use futures::future::ok;
|
||||
use futures::{future, Async, Future, IntoFuture, Poll};
|
||||
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::request::HttpRequest;
|
||||
@ -15,7 +17,7 @@ pub trait FromRequest: Sized {
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// Future that resolves to a Self
|
||||
type Future: IntoFuture<Item = Self, Error = Self::Error>;
|
||||
type Future: Future<Output = Result<Self, Self::Error>>;
|
||||
|
||||
/// Configuration for this extractor
|
||||
type Config: Default + 'static;
|
||||
@ -48,6 +50,7 @@ pub trait FromRequest: Sized {
|
||||
/// ```rust
|
||||
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
/// use futures::future::{ok, err, Ready};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// use rand;
|
||||
///
|
||||
@ -58,14 +61,14 @@ pub trait FromRequest: Sized {
|
||||
///
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Self, Self::Error>;
|
||||
/// type Future = Ready<Result<Self, Self::Error>>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
/// Err(ErrorBadRequest("no luck"))
|
||||
/// err(ErrorBadRequest("no luck"))
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
@ -94,21 +97,19 @@ where
|
||||
{
|
||||
type Config = T::Config;
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Option<T>, Error = Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Option<T>, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
.then(|r| match r {
|
||||
Ok(v) => future::ok(Some(v)),
|
||||
Err(e) => {
|
||||
log::debug!("Error for Option<T> extractor: {}", e.into());
|
||||
future::ok(None)
|
||||
}
|
||||
}),
|
||||
)
|
||||
T::from_request(req, payload)
|
||||
.then(|r| match r {
|
||||
Ok(v) => ok(Some(v)),
|
||||
Err(e) => {
|
||||
log::debug!("Error for Option<T> extractor: {}", e.into());
|
||||
ok(None)
|
||||
}
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +122,7 @@ where
|
||||
/// ```rust
|
||||
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
/// use futures::future::{ok, err, Ready};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// use rand;
|
||||
///
|
||||
@ -131,14 +133,14 @@ where
|
||||
///
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Thing, Error>;
|
||||
/// type Future = Ready<Result<Thing, Error>>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
/// Err(ErrorBadRequest("no luck"))
|
||||
/// err(ErrorBadRequest("no luck"))
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
@ -157,26 +159,24 @@ where
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<T: 'static> FromRequest for Result<T, T::Error>
|
||||
impl<T> FromRequest for Result<T, T::Error>
|
||||
where
|
||||
T: FromRequest,
|
||||
T::Future: 'static,
|
||||
T: FromRequest + 'static,
|
||||
T::Error: 'static,
|
||||
T::Future: 'static,
|
||||
{
|
||||
type Config = T::Config;
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Result<T, T::Error>, Error = Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Result<T, T::Error>, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
.then(|res| match res {
|
||||
Ok(v) => ok(Ok(v)),
|
||||
Err(e) => ok(Err(e)),
|
||||
}),
|
||||
)
|
||||
T::from_request(req, payload)
|
||||
.then(|res| match res {
|
||||
Ok(v) => ok(Ok(v)),
|
||||
Err(e) => ok(Err(e)),
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,10 +184,10 @@ where
|
||||
impl FromRequest for () {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<(), Error>;
|
||||
type Future = Ready<Result<(), Error>>;
|
||||
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(())
|
||||
ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,43 +204,44 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$fut_type {
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: ($($T::from_request(req, payload).into_future(),)+),
|
||||
futs: ($($T::from_request(req, payload),)+),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[pin_project::pin_project]
|
||||
pub struct $fut_type<$($T: FromRequest),+> {
|
||||
items: ($(Option<$T>,)+),
|
||||
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
|
||||
futs: ($($T::Future,)+),
|
||||
}
|
||||
|
||||
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
|
||||
{
|
||||
type Item = ($($T,)+);
|
||||
type Error = Error;
|
||||
type Output = Result<($($T,)+), Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut ready = true;
|
||||
|
||||
$(
|
||||
if self.items.$n.is_none() {
|
||||
match self.futs.$n.poll() {
|
||||
Ok(Async::Ready(item)) => {
|
||||
self.items.$n = Some(item);
|
||||
if this.items.$n.is_none() {
|
||||
match unsafe { Pin::new_unchecked(&mut this.futs.$n) }.poll(cx) {
|
||||
Poll::Ready(Ok(item)) => {
|
||||
this.items.$n = Some(item);
|
||||
}
|
||||
Ok(Async::NotReady) => ready = false,
|
||||
Err(e) => return Err(e.into()),
|
||||
Poll::Pending => ready = false,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
||||
if ready {
|
||||
Ok(Async::Ready(
|
||||
($(self.items.$n.take().unwrap(),)+)
|
||||
Poll::Ready(Ok(
|
||||
($(this.items.$n.take().unwrap(),)+)
|
||||
))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
src/handler.rs
239
src/handler.rs
@ -1,10 +1,14 @@
|
||||
use std::convert::Infallible;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::{Error, Response};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures::future::{ok, Ready};
|
||||
use futures::ready;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
@ -73,14 +77,14 @@ where
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Infallible;
|
||||
type Future = HandlerServiceResponse<<R::Future as IntoFuture>::Future>;
|
||||
type Future = HandlerServiceResponse<R>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
|
||||
let fut = self.hnd.call(param).respond_to(&req).into_future();
|
||||
let fut = self.hnd.call(param).respond_to(&req);
|
||||
HandlerServiceResponse {
|
||||
fut,
|
||||
req: Some(req),
|
||||
@ -88,53 +92,48 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HandlerServiceResponse<T> {
|
||||
fut: T,
|
||||
#[pin_project]
|
||||
pub struct HandlerServiceResponse<T: Responder> {
|
||||
#[pin]
|
||||
fut: T::Future,
|
||||
req: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
impl<T> Future for HandlerServiceResponse<T>
|
||||
where
|
||||
T: Future<Item = Response>,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = Infallible;
|
||||
impl<T: Responder> Future for HandlerServiceResponse<T> {
|
||||
type Output = Result<ServiceResponse, Infallible>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
))),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match this.fut.poll(cx) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(e)) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Async handler converter factory
|
||||
pub trait AsyncFactory<T, R>: Clone + 'static
|
||||
pub trait AsyncFactory<T, R, O, E>: Clone + 'static
|
||||
where
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
impl<F, R> AsyncFactory<(), R> for F
|
||||
impl<F, R, O, E> AsyncFactory<(), R, O, E> for F
|
||||
where
|
||||
F: Fn() -> R + Clone + 'static,
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn call(&self, _: ()) -> R {
|
||||
(self)()
|
||||
@ -142,23 +141,23 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandler<F, T, R>
|
||||
pub struct AsyncHandler<F, T, R, O, E>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
F: AsyncFactory<T, R, O, E>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
_t: PhantomData<(T, R, O, E)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> AsyncHandler<F, T, R>
|
||||
impl<F, T, R, O, E> AsyncHandler<F, T, R, O, E>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
F: AsyncFactory<T, R, O, E>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
pub fn new(hnd: F) -> Self {
|
||||
AsyncHandler {
|
||||
@ -168,12 +167,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, R> Clone for AsyncHandler<F, T, R>
|
||||
impl<F, T, R, O, E> Clone for AsyncHandler<F, T, R, O, E>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
F: AsyncFactory<T, R, O, E>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
AsyncHandler {
|
||||
@ -183,25 +182,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, R> Service for AsyncHandler<F, T, R>
|
||||
impl<F, T, R, O, E> Service for AsyncHandler<F, T, R, O, E>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
F: AsyncFactory<T, R, O, E>,
|
||||
R: Future<Output = Result<O, E>>,
|
||||
O: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Infallible;
|
||||
type Future = AsyncHandlerServiceResponse<R::Future>;
|
||||
type Future = AsyncHandlerServiceResponse<R, O, E>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
|
||||
AsyncHandlerServiceResponse {
|
||||
fut: self.hnd.call(param).into_future(),
|
||||
fut: self.hnd.call(param),
|
||||
fut2: None,
|
||||
req: Some(req),
|
||||
}
|
||||
@ -209,56 +208,54 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandlerServiceResponse<T>
|
||||
#[pin_project]
|
||||
pub struct AsyncHandlerServiceResponse<T, R, E>
|
||||
where
|
||||
T: Future,
|
||||
T::Item: Responder,
|
||||
T: Future<Output = Result<R, E>>,
|
||||
R: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
#[pin]
|
||||
fut: T,
|
||||
fut2: Option<<<T::Item as Responder>::Future as IntoFuture>::Future>,
|
||||
#[pin]
|
||||
fut2: Option<R::Future>,
|
||||
req: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
impl<T> Future for AsyncHandlerServiceResponse<T>
|
||||
impl<T, R, E> Future for AsyncHandlerServiceResponse<T, R, E>
|
||||
where
|
||||
T: Future,
|
||||
T::Item: Responder,
|
||||
T::Error: Into<Error>,
|
||||
T: Future<Output = Result<R, E>>,
|
||||
R: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = Infallible;
|
||||
type Output = Result<ServiceResponse, Infallible>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut2 {
|
||||
return match fut.poll() {
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
))),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
|
||||
if let Some(fut) = this.fut2.as_pin_mut() {
|
||||
return match fut.poll(cx) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(e)) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match self.fut.poll() {
|
||||
Ok(Async::Ready(res)) => {
|
||||
self.fut2 =
|
||||
Some(res.respond_to(self.req.as_ref().unwrap()).into_future());
|
||||
self.poll()
|
||||
match this.fut.poll(cx) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
let fut = res.respond_to(this.req.as_ref().unwrap());
|
||||
self.as_mut().project().fut2.set(Some(fut));
|
||||
self.poll(cx)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(e)) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,7 +276,7 @@ impl<T: FromRequest, S> Extract<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromRequest, S> NewService for Extract<T, S>
|
||||
impl<T: FromRequest, S> ServiceFactory for Extract<T, S>
|
||||
where
|
||||
S: Service<
|
||||
Request = (T, HttpRequest),
|
||||
@ -293,7 +290,7 @@ where
|
||||
type Error = (Error, ServiceRequest);
|
||||
type InitError = ();
|
||||
type Service = ExtractService<T, S>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
type Future = Ready<Result<Self::Service, ()>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(ExtractService {
|
||||
@ -321,13 +318,13 @@ where
|
||||
type Error = (Error, ServiceRequest);
|
||||
type Future = ExtractResponse<T, S>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let (req, mut payload) = req.into_parts();
|
||||
let fut = T::from_request(&req, &mut payload).into_future();
|
||||
let fut = T::from_request(&req, &mut payload);
|
||||
|
||||
ExtractResponse {
|
||||
fut,
|
||||
@ -338,10 +335,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct ExtractResponse<T: FromRequest, S: Service> {
|
||||
req: HttpRequest,
|
||||
service: S,
|
||||
fut: <T::Future as IntoFuture>::Future,
|
||||
#[pin]
|
||||
fut: T::Future,
|
||||
#[pin]
|
||||
fut_s: Option<S::Future>,
|
||||
}
|
||||
|
||||
@ -353,21 +353,26 @@ where
|
||||
Error = Infallible,
|
||||
>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest);
|
||||
type Output = Result<ServiceResponse, (Error, ServiceRequest)>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut_s {
|
||||
return fut.poll().map_err(|_| panic!());
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
|
||||
if let Some(fut) = this.fut_s.as_pin_mut() {
|
||||
return fut.poll(cx).map_err(|_| panic!());
|
||||
}
|
||||
|
||||
let item = try_ready!(self.fut.poll().map_err(|e| {
|
||||
let req = ServiceRequest::new(self.req.clone());
|
||||
(e.into(), req)
|
||||
}));
|
||||
|
||||
self.fut_s = Some(self.service.call((item, self.req.clone())));
|
||||
self.poll()
|
||||
match ready!(this.fut.poll(cx)) {
|
||||
Err(e) => {
|
||||
let req = ServiceRequest::new(this.req.clone());
|
||||
Poll::Ready(Err((e.into(), req)))
|
||||
}
|
||||
Ok(item) => {
|
||||
let fut = Some(this.service.call((item, this.req.clone())));
|
||||
self.as_mut().project().fut_s.set(fut);
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,11 +387,11 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
|
||||
impl<Func, $($T,)+ Res, O, E1> AsyncFactory<($($T,)+), Res, O, E1> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
Res: IntoFuture,
|
||||
Res::Item: Responder,
|
||||
Res::Error: Into<Error>,
|
||||
Res: Future<Output = Result<O, E1>>,
|
||||
O: Responder,
|
||||
E1: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: ($($T,)+)) -> Res {
|
||||
(self)($(param.$n,)+)
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -1,4 +1,4 @@
|
||||
#![allow(clippy::borrow_interior_mutable_const)]
|
||||
#![allow(clippy::borrow_interior_mutable_const, unused_imports, dead_code)]
|
||||
//! Actix web is a small, pragmatic, and extremely fast web framework
|
||||
//! for Rust.
|
||||
//!
|
||||
@ -68,8 +68,8 @@
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `client` - enables http client (default enabled)
|
||||
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
|
||||
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
|
||||
//! * `openssl` - enables ssl support via `openssl` crate, supports `http/2`
|
||||
//! * `rustls` - enables ssl support via `rustls` crate, supports `http/2`
|
||||
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
|
||||
//! dependency
|
||||
//! * `brotli` - enables `brotli` compression support, requires `c`
|
||||
@ -78,7 +78,6 @@
|
||||
//! `c` compiler (default enabled)
|
||||
//! * `flate2-rust` - experimental rust based implementation for
|
||||
//! `gzip`, `deflate` compression.
|
||||
//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method.
|
||||
//!
|
||||
#![allow(clippy::type_complexity, clippy::new_without_default)]
|
||||
|
||||
@ -143,9 +142,10 @@ pub mod dev {
|
||||
pub use crate::service::{
|
||||
HttpServiceFactory, ServiceRequest, ServiceResponse, WebService,
|
||||
};
|
||||
pub use crate::types::form::UrlEncoded;
|
||||
pub use crate::types::json::JsonBody;
|
||||
pub use crate::types::readlines::Readlines;
|
||||
|
||||
//pub use crate::types::form::UrlEncoded;
|
||||
//pub use crate::types::json::JsonBody;
|
||||
//pub use crate::types::readlines::Readlines;
|
||||
|
||||
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream};
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
@ -176,18 +176,16 @@ pub mod client {
|
||||
//! use actix_web::client::Client;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! System::new("test").block_on(lazy(|| {
|
||||
//! System::new("test").block_on(async {
|
||||
//! let mut client = Client::default();
|
||||
//!
|
||||
//! client.get("http://www.rust-lang.org") // <- Create request builder
|
||||
//! // Create request builder and send request
|
||||
//! let response = client.get("http://www.rust-lang.org")
|
||||
//! .header("User-Agent", "Actix-web")
|
||||
//! .send() // <- Send http request
|
||||
//! .map_err(|_| ())
|
||||
//! .and_then(|response| { // <- server http response
|
||||
//! println!("Response: {:?}", response);
|
||||
//! Ok(())
|
||||
//! })
|
||||
//! }));
|
||||
//! .send().await; // <- Send http request
|
||||
//!
|
||||
//! println!("Response: {:?}", response);
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
pub use awc::error::{
|
||||
|
@ -1,15 +1,18 @@
|
||||
//! `Middleware` for compressing response body.
|
||||
use std::cmp;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_http::encoding::Encoder;
|
||||
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING};
|
||||
use actix_http::{Error, Response, ResponseBuilder};
|
||||
use actix_service::{Service, Transform};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::future::{ok, Ready};
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
@ -78,7 +81,7 @@ where
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Transform = CompressMiddleware<S>;
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(CompressMiddleware {
|
||||
@ -103,8 +106,8 @@ where
|
||||
type Error = Error;
|
||||
type Future = CompressResponse<S, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
@ -128,11 +131,13 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[pin_project]
|
||||
pub struct CompressResponse<S, B>
|
||||
where
|
||||
S: Service,
|
||||
B: MessageBody,
|
||||
{
|
||||
#[pin]
|
||||
fut: S::Future,
|
||||
encoding: ContentEncoding,
|
||||
_t: PhantomData<(B)>,
|
||||
@ -143,21 +148,25 @@ where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Item = ServiceResponse<Encoder<B>>;
|
||||
type Error = Error;
|
||||
type Output = Result<ServiceResponse<Encoder<B>>, Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let resp = futures::try_ready!(self.fut.poll());
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
|
||||
enc.0
|
||||
} else {
|
||||
self.encoding
|
||||
};
|
||||
match futures::ready!(this.fut.poll(cx)) {
|
||||
Ok(resp) => {
|
||||
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
|
||||
enc.0
|
||||
} else {
|
||||
*this.encoding
|
||||
};
|
||||
|
||||
Ok(Async::Ready(resp.map_body(move |head, body| {
|
||||
Encoder::response(enc, head, body)
|
||||
})))
|
||||
Poll::Ready(Ok(
|
||||
resp.map_body(move |head, body| Encoder::response(enc, head, body))
|
||||
))
|
||||
}
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
//! Middleware for setting default response headers
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_service::{Service, Transform};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Future, Poll};
|
||||
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
|
||||
|
||||
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
use crate::http::{HeaderMap, HttpTryFrom};
|
||||
@ -96,7 +98,7 @@ where
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Transform = DefaultHeadersMiddleware<S>;
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(DefaultHeadersMiddleware {
|
||||
@ -119,16 +121,19 @@ where
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
let fut = self.service.call(req);
|
||||
|
||||
async move {
|
||||
let mut res = fut.await?;
|
||||
|
||||
Box::new(self.service.call(req).map(move |mut res| {
|
||||
// set response headers
|
||||
for (key, value) in inner.headers.iter() {
|
||||
if !res.headers().contains_key(key) {
|
||||
@ -142,15 +147,16 @@ where
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
}))
|
||||
Ok(res)
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::IntoService;
|
||||
use futures::future::ok;
|
||||
|
||||
use super::*;
|
||||
use crate::dev::ServiceRequest;
|
||||
@ -160,46 +166,50 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let mut mw = block_on(
|
||||
DefaultHeaders::new()
|
||||
block_on(async {
|
||||
let mut mw = DefaultHeaders::new()
|
||||
.header(CONTENT_TYPE, "0001")
|
||||
.new_transform(ok_service()),
|
||||
)
|
||||
.unwrap();
|
||||
.new_transform(ok_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let resp = block_on(mw.call(req)).unwrap();
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let resp = mw.call(req).await.unwrap();
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let srv = |req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
|
||||
};
|
||||
let mut mw = block_on(
|
||||
DefaultHeaders::new()
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(
|
||||
HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(),
|
||||
))
|
||||
};
|
||||
let mut mw = DefaultHeaders::new()
|
||||
.header(CONTENT_TYPE, "0001")
|
||||
.new_transform(srv.into_service()),
|
||||
)
|
||||
.unwrap();
|
||||
let resp = block_on(mw.call(req)).unwrap();
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = mw.call(req).await.unwrap();
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_type() {
|
||||
let srv = |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish());
|
||||
let mut mw = block_on(
|
||||
DefaultHeaders::new()
|
||||
block_on(async {
|
||||
let srv =
|
||||
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
|
||||
let mut mw = DefaultHeaders::new()
|
||||
.content_type()
|
||||
.new_transform(srv.into_service()),
|
||||
)
|
||||
.unwrap();
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let resp = block_on(mw.call(req)).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let resp = mw.call(req).await.unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,15 @@
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_service::{Service, Transform};
|
||||
use bytes::Bytes;
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::future::{ok, Ready};
|
||||
use log::debug;
|
||||
use regex::Regex;
|
||||
use time;
|
||||
@ -125,7 +127,7 @@ where
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Transform = LoggerMiddleware<S>;
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(LoggerMiddleware {
|
||||
@ -151,8 +153,8 @@ where
|
||||
type Error = Error;
|
||||
type Future = LoggerResponse<S, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
@ -181,11 +183,13 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[pin_project::pin_project]
|
||||
pub struct LoggerResponse<S, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service,
|
||||
{
|
||||
#[pin]
|
||||
fut: S::Future,
|
||||
time: time::Tm,
|
||||
format: Option<Format>,
|
||||
@ -197,11 +201,15 @@ where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Item = ServiceResponse<StreamLog<B>>;
|
||||
type Error = Error;
|
||||
type Output = Result<ServiceResponse<StreamLog<B>>, Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let res = futures::try_ready!(self.fut.poll());
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let res = match futures::ready!(this.fut.poll(cx)) {
|
||||
Ok(res) => res,
|
||||
Err(e) => return Poll::Ready(Err(e)),
|
||||
};
|
||||
|
||||
if let Some(error) = res.response().error() {
|
||||
if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR {
|
||||
@ -209,18 +217,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut format) = self.format {
|
||||
if let Some(ref mut format) = this.format {
|
||||
for unit in &mut format.0 {
|
||||
unit.render_response(res.response());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Async::Ready(res.map_body(move |_, body| {
|
||||
let time = *this.time;
|
||||
let format = this.format.take();
|
||||
|
||||
Poll::Ready(Ok(res.map_body(move |_, body| {
|
||||
ResponseBody::Body(StreamLog {
|
||||
body,
|
||||
time,
|
||||
format,
|
||||
size: 0,
|
||||
time: self.time,
|
||||
format: self.format.take(),
|
||||
})
|
||||
})))
|
||||
}
|
||||
@ -252,13 +263,13 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
|
||||
self.body.size()
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
match self.body.poll_next()? {
|
||||
Async::Ready(Some(chunk)) => {
|
||||
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> {
|
||||
match self.body.poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(chunk))) => {
|
||||
self.size += chunk.len();
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
Poll::Ready(Some(Ok(chunk)))
|
||||
}
|
||||
val => Ok(val),
|
||||
val => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -464,6 +475,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::{IntoService, Service, Transform};
|
||||
use futures::future::ok;
|
||||
|
||||
use super::*;
|
||||
use crate::http::{header, StatusCode};
|
||||
@ -472,11 +484,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_logger() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
req.into_response(
|
||||
ok(req.into_response(
|
||||
HttpResponse::build(StatusCode::OK)
|
||||
.header("X-Test", "ttt")
|
||||
.finish(),
|
||||
)
|
||||
))
|
||||
};
|
||||
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
|
||||
|
||||
|
@ -2,13 +2,13 @@
|
||||
mod compress;
|
||||
pub use self::compress::{BodyEncoding, Compress};
|
||||
|
||||
mod condition;
|
||||
//mod condition;
|
||||
mod defaultheaders;
|
||||
pub mod errhandlers;
|
||||
//pub mod errhandlers;
|
||||
mod logger;
|
||||
mod normalize;
|
||||
//mod normalize;
|
||||
|
||||
pub use self::condition::Condition;
|
||||
//pub use self::condition::Condition;
|
||||
pub use self::defaultheaders::DefaultHeaders;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::normalize::NormalizePath;
|
||||
//pub use self::normalize::NormalizePath;
|
||||
|
114
src/request.rs
114
src/request.rs
@ -5,6 +5,7 @@ use std::{fmt, net};
|
||||
use actix_http::http::{HeaderMap, Method, Uri, Version};
|
||||
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
||||
use actix_router::{Path, Url};
|
||||
use futures::future::{ok, Ready};
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::data::Data;
|
||||
@ -289,11 +290,11 @@ impl Drop for HttpRequest {
|
||||
impl FromRequest for HttpRequest {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(req.clone())
|
||||
ok(req.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +350,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::dev::{ResourceDef, ResourceMap};
|
||||
use crate::http::{header, StatusCode};
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::test::{block_on, call_service, init_service, TestRequest};
|
||||
use crate::{web, App, HttpResponse};
|
||||
|
||||
#[test]
|
||||
@ -467,66 +468,73 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_app_data() {
|
||||
let mut srv = init_service(App::new().data(10usize).service(
|
||||
web::resource("/").to(|req: HttpRequest| {
|
||||
if req.app_data::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
}),
|
||||
));
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().data(10usize).service(
|
||||
web::resource("/").to(|req: HttpRequest| {
|
||||
if req.app_data::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
web::resource("/").to(|req: HttpRequest| {
|
||||
if req.app_data::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
}),
|
||||
));
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
web::resource("/").to(|req: HttpRequest| {
|
||||
if req.app_data::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extensions_dropped() {
|
||||
struct Tracker {
|
||||
pub dropped: bool,
|
||||
}
|
||||
struct Foo {
|
||||
tracker: Rc<RefCell<Tracker>>,
|
||||
}
|
||||
impl Drop for Foo {
|
||||
fn drop(&mut self) {
|
||||
self.tracker.borrow_mut().dropped = true;
|
||||
block_on(async {
|
||||
struct Tracker {
|
||||
pub dropped: bool,
|
||||
}
|
||||
struct Foo {
|
||||
tracker: Rc<RefCell<Tracker>>,
|
||||
}
|
||||
impl Drop for Foo {
|
||||
fn drop(&mut self) {
|
||||
self.tracker.borrow_mut().dropped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
|
||||
{
|
||||
let tracker2 = Rc::clone(&tracker);
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
web::resource("/").to(move |req: HttpRequest| {
|
||||
req.extensions_mut().insert(Foo {
|
||||
tracker: Rc::clone(&tracker2),
|
||||
});
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
));
|
||||
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
|
||||
{
|
||||
let tracker2 = Rc::clone(&tracker);
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
web::resource("/").to(move |req: HttpRequest| {
|
||||
req.extensions_mut().insert(Foo {
|
||||
tracker: Rc::clone(&tracker2),
|
||||
});
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
assert!(tracker.borrow().dropped);
|
||||
assert!(tracker.borrow().dropped);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
441
src/resource.rs
441
src/resource.rs
@ -1,14 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::{Error, Extensions, Response};
|
||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||
use actix_service::{
|
||||
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
|
||||
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform,
|
||||
};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
use futures::future::{ok, Either, LocalBoxFuture, Ready};
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::data::Data;
|
||||
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
|
||||
@ -74,7 +77,7 @@ impl Resource {
|
||||
|
||||
impl<T> Resource<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -243,8 +246,8 @@ where
|
||||
/// use actix_web::*;
|
||||
/// use futures::future::{ok, Future};
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> impl Future<Item=HttpResponse, Error=Error> {
|
||||
/// ok(HttpResponse::Ok().finish())
|
||||
/// async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// }
|
||||
///
|
||||
/// App::new().service(web::resource("/").to_async(index));
|
||||
@ -255,19 +258,19 @@ where
|
||||
/// ```rust
|
||||
/// # use actix_web::*;
|
||||
/// # use futures::future::Future;
|
||||
/// # fn index(req: HttpRequest) -> Box<dyn Future<Item=HttpResponse, Error=Error>> {
|
||||
/// # async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// App::new().service(web::resource("/").route(web::route().to_async(index)));
|
||||
/// ```
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_async<F, I, R>(mut self, handler: F) -> Self
|
||||
pub fn to_async<F, I, R, O, E>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: AsyncFactory<I, R>,
|
||||
F: AsyncFactory<I, R, O, E>,
|
||||
I: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
R: Future<Output = Result<O, E>> + 'static,
|
||||
O: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
self.routes.push(Route::new().to_async(handler));
|
||||
self
|
||||
@ -280,11 +283,11 @@ where
|
||||
/// type (i.e modify response's body).
|
||||
///
|
||||
/// **Note**: middlewares get called in opposite order of middlewares registration.
|
||||
pub fn wrap<M, F>(
|
||||
pub fn wrap<M>(
|
||||
self,
|
||||
mw: F,
|
||||
mw: M,
|
||||
) -> Resource<
|
||||
impl NewService<
|
||||
impl ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -300,11 +303,9 @@ where
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
F: IntoTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = apply_transform(mw, self.endpoint);
|
||||
Resource {
|
||||
endpoint,
|
||||
endpoint: apply(mw, self.endpoint),
|
||||
rdef: self.rdef,
|
||||
name: self.name,
|
||||
guards: self.guards,
|
||||
@ -337,13 +338,16 @@ where
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .wrap_fn(|req, srv|
|
||||
/// srv.call(req).map(|mut res| {
|
||||
/// .wrap_fn(|req, srv| {
|
||||
/// let fut = srv.call(req);
|
||||
/// async {
|
||||
/// let mut res = fut.await?;
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// res
|
||||
/// }))
|
||||
/// Ok(res)
|
||||
/// }
|
||||
/// })
|
||||
/// .route(web::get().to(index)));
|
||||
/// }
|
||||
/// ```
|
||||
@ -351,7 +355,7 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> Resource<
|
||||
impl NewService<
|
||||
impl ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -361,9 +365,18 @@ where
|
||||
>
|
||||
where
|
||||
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse, Error = Error>,
|
||||
R: Future<Output = Result<ServiceResponse, Error>>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
Resource {
|
||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||
rdef: self.rdef,
|
||||
name: self.name,
|
||||
guards: self.guards,
|
||||
routes: self.routes,
|
||||
default: self.default,
|
||||
data: self.data,
|
||||
factory_ref: self.factory_ref,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default service to be used if no matching route could be found.
|
||||
@ -371,8 +384,8 @@ where
|
||||
/// default handler from `App` or `Scope`.
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
F: IntoServiceFactory<U>,
|
||||
U: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -381,8 +394,8 @@ where
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
|
||||
f.into_new_service().map_init_err(|e| {
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
||||
f.into_factory().map_init_err(|e| {
|
||||
log::error!("Can not construct default service: {:?}", e)
|
||||
}),
|
||||
)))));
|
||||
@ -393,7 +406,7 @@ where
|
||||
|
||||
impl<T> HttpServiceFactory for Resource<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -423,9 +436,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoNewService<T> for Resource<T>
|
||||
impl<T> IntoServiceFactory<T> for Resource<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -433,7 +446,7 @@ where
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
fn into_new_service(self) -> T {
|
||||
fn into_factory(self) -> T {
|
||||
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
||||
routes: self.routes,
|
||||
data: self.data.map(Rc::new),
|
||||
@ -450,7 +463,7 @@ pub struct ResourceFactory {
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
}
|
||||
|
||||
impl NewService for ResourceFactory {
|
||||
impl ServiceFactory for ResourceFactory {
|
||||
type Config = ();
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
@ -488,31 +501,30 @@ pub struct CreateResourceService {
|
||||
fut: Vec<CreateRouteServiceItem>,
|
||||
data: Option<Rc<Extensions>>,
|
||||
default: Option<HttpService>,
|
||||
default_fut: Option<Box<dyn Future<Item = HttpService, Error = ()>>>,
|
||||
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
|
||||
}
|
||||
|
||||
impl Future for CreateResourceService {
|
||||
type Item = ResourceService;
|
||||
type Error = ();
|
||||
type Output = Result<ResourceService, ()>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
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 fut.poll()? {
|
||||
Async::Ready(default) => self.default = Some(default),
|
||||
Async::NotReady => done = false,
|
||||
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 fut.poll()? {
|
||||
Async::Ready(route) => {
|
||||
*item = CreateRouteServiceItem::Service(route)
|
||||
}
|
||||
Async::NotReady => {
|
||||
CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut)
|
||||
.poll(cx)?
|
||||
{
|
||||
Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route),
|
||||
Poll::Pending => {
|
||||
done = false;
|
||||
}
|
||||
},
|
||||
@ -529,13 +541,13 @@ impl Future for CreateResourceService {
|
||||
CreateRouteServiceItem::Future(_) => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
Ok(Async::Ready(ResourceService {
|
||||
Poll::Ready(Ok(ResourceService {
|
||||
routes,
|
||||
data: self.data.clone(),
|
||||
default: self.default.take(),
|
||||
}))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -551,12 +563,12 @@ impl Service for ResourceService {
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<dyn Future<Item = ServiceResponse, Error = Error>>,
|
||||
Ready<Result<ServiceResponse, Error>>,
|
||||
LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
@ -565,14 +577,14 @@ impl Service for ResourceService {
|
||||
if let Some(ref data) = self.data {
|
||||
req.set_data_container(data.clone());
|
||||
}
|
||||
return route.call(req);
|
||||
return Either::Right(route.call(req));
|
||||
}
|
||||
}
|
||||
if let Some(ref mut default) = self.default {
|
||||
default.call(req)
|
||||
Either::Right(default.call(req))
|
||||
} else {
|
||||
let req = req.into_parts().0;
|
||||
Either::A(ok(ServiceResponse::new(
|
||||
Either::Left(ok(ServiceResponse::new(
|
||||
req,
|
||||
Response::MethodNotAllowed().finish(),
|
||||
)))
|
||||
@ -591,7 +603,7 @@ impl ResourceEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl NewService for ResourceEndpoint {
|
||||
impl ServiceFactory for ResourceEndpoint {
|
||||
type Config = ();
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
@ -610,18 +622,19 @@ mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_service::Service;
|
||||
use futures::{Future, IntoFuture};
|
||||
use tokio_timer::sleep;
|
||||
use futures::future::{ok, Future};
|
||||
use tokio_timer::delay_for;
|
||||
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::middleware::DefaultHeaders;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::test::{block_on, call_service, init_service, TestRequest};
|
||||
use crate::{guard, web, App, Error, HttpResponse};
|
||||
|
||||
fn md<S, B>(
|
||||
req: ServiceRequest,
|
||||
srv: &mut S,
|
||||
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
|
||||
) -> impl Future<Output = Result<ServiceResponse<B>, Error>>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest,
|
||||
@ -629,178 +642,210 @@ mod tests {
|
||||
Error = Error,
|
||||
>,
|
||||
{
|
||||
srv.call(req).map(|mut res| {
|
||||
let fut = srv.call(req);
|
||||
async move {
|
||||
let mut res = fut.await?;
|
||||
res.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
res
|
||||
})
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.name("test")
|
||||
.wrap(md)
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.name("test")
|
||||
.wrap(DefaultHeaders::new().header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
))
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_fn() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.wrap_fn(|req, srv| {
|
||||
srv.call(req).map(|mut res| {
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
res
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.wrap_fn(|req, srv| {
|
||||
let fut = srv.call(req);
|
||||
async {
|
||||
fut.await.map(|mut res| {
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
);
|
||||
res
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("0001")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_async() {
|
||||
let mut srv =
|
||||
init_service(App::new().service(web::resource("/test").to_async(|| {
|
||||
sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok())
|
||||
})));
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
block_on(async {
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::resource("/test").to_async(|| {
|
||||
async {
|
||||
delay_for(Duration::from_millis(100)).await;
|
||||
Ok::<_, Error>(HttpResponse::Ok())
|
||||
}
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::BadRequest())
|
||||
}),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::BadRequest())
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_guards() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Get())
|
||||
.to(|| HttpResponse::Ok()),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Put())
|
||||
.to(|| HttpResponse::Created()),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Delete())
|
||||
.to(|| HttpResponse::NoContent()),
|
||||
),
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Get())
|
||||
.to(|| HttpResponse::Ok()),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Put())
|
||||
.to(|| HttpResponse::Created()),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Delete())
|
||||
.to(|| HttpResponse::NoContent()),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::DELETE)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
let req = TestRequest::with_uri("/test/it")
|
||||
.method(Method::DELETE)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.data(1.0f64)
|
||||
.data(1usize)
|
||||
.register_data(web::Data::new('-'))
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.data(10usize)
|
||||
.register_data(web::Data::new('*'))
|
||||
.guard(guard::Get())
|
||||
.to(
|
||||
|data1: web::Data<usize>,
|
||||
data2: web::Data<char>,
|
||||
data3: web::Data<f64>| {
|
||||
assert_eq!(*data1, 10);
|
||||
assert_eq!(*data2, '*');
|
||||
assert_eq!(*data3, 1.0);
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.data(1.0f64)
|
||||
.data(1usize)
|
||||
.register_data(web::Data::new('-'))
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.data(10usize)
|
||||
.register_data(web::Data::new('*'))
|
||||
.guard(guard::Get())
|
||||
.to(
|
||||
|data1: web::Data<usize>,
|
||||
data2: web::Data<char>,
|
||||
data3: web::Data<f64>| {
|
||||
assert_eq!(*data1, 10);
|
||||
assert_eq!(*data2, '*');
|
||||
assert_eq!(*data3, 1.0);
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::get().uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::get().uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
438
src/responder.rs
438
src/responder.rs
@ -1,3 +1,8 @@
|
||||
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::http::{
|
||||
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom,
|
||||
@ -5,8 +10,9 @@ use actix_http::http::{
|
||||
};
|
||||
use actix_http::{Error, Response, ResponseBuilder};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{err, ok, Either as EitherFuture, FutureResult};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
use futures::future::{err, ok, Either as EitherFuture, LocalBoxFuture, Ready};
|
||||
use futures::ready;
|
||||
use pin_project::{pin_project, project};
|
||||
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
@ -18,7 +24,7 @@ pub trait Responder {
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// The future response value.
|
||||
type Future: IntoFuture<Item = Response, Error = Self::Error>;
|
||||
type Future: Future<Output = Result<Response, Self::Error>>;
|
||||
|
||||
/// Convert itself to `AsyncResult` or `Error`.
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future;
|
||||
@ -71,7 +77,7 @@ pub trait Responder {
|
||||
|
||||
impl Responder for Response {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
@ -84,15 +90,14 @@ where
|
||||
T: Responder,
|
||||
{
|
||||
type Error = T::Error;
|
||||
type Future = EitherFuture<
|
||||
<T::Future as IntoFuture>::Future,
|
||||
FutureResult<Response, T::Error>,
|
||||
>;
|
||||
type Future = EitherFuture<T::Future, Ready<Result<Response, T::Error>>>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Some(t) => EitherFuture::A(t.respond_to(req).into_future()),
|
||||
None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())),
|
||||
Some(t) => EitherFuture::Left(t.respond_to(req)),
|
||||
None => {
|
||||
EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,23 +109,21 @@ where
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = EitherFuture<
|
||||
ResponseFuture<<T::Future as IntoFuture>::Future>,
|
||||
FutureResult<Response, Error>,
|
||||
ResponseFuture<T::Future, T::Error>,
|
||||
Ready<Result<Response, Error>>,
|
||||
>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Ok(val) => {
|
||||
EitherFuture::A(ResponseFuture::new(val.respond_to(req).into_future()))
|
||||
}
|
||||
Err(e) => EitherFuture::B(err(e.into())),
|
||||
Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))),
|
||||
Err(e) => EitherFuture::Right(err(e.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for ResponseBuilder {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: &HttpRequest) -> Self::Future {
|
||||
@ -130,7 +133,7 @@ impl Responder for ResponseBuilder {
|
||||
|
||||
impl Responder for () {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK).finish())
|
||||
@ -146,7 +149,7 @@ where
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
CustomResponderFut {
|
||||
fut: self.0.respond_to(req).into_future(),
|
||||
fut: self.0.respond_to(req),
|
||||
status: Some(self.1),
|
||||
headers: None,
|
||||
}
|
||||
@ -155,7 +158,7 @@ where
|
||||
|
||||
impl Responder for &'static str {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -166,7 +169,7 @@ impl Responder for &'static str {
|
||||
|
||||
impl Responder for &'static [u8] {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -177,7 +180,7 @@ impl Responder for &'static [u8] {
|
||||
|
||||
impl Responder for String {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -188,7 +191,7 @@ impl Responder for String {
|
||||
|
||||
impl<'a> Responder for &'a String {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -199,7 +202,7 @@ impl<'a> Responder for &'a String {
|
||||
|
||||
impl Responder for Bytes {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -210,7 +213,7 @@ impl Responder for Bytes {
|
||||
|
||||
impl Responder for BytesMut {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
@ -299,34 +302,40 @@ impl<T: Responder> Responder for CustomResponder<T> {
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
CustomResponderFut {
|
||||
fut: self.responder.respond_to(req).into_future(),
|
||||
fut: self.responder.respond_to(req),
|
||||
status: self.status,
|
||||
headers: self.headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct CustomResponderFut<T: Responder> {
|
||||
fut: <T::Future as IntoFuture>::Future,
|
||||
#[pin]
|
||||
fut: T::Future,
|
||||
status: Option<StatusCode>,
|
||||
headers: Option<HeaderMap>,
|
||||
}
|
||||
|
||||
impl<T: Responder> Future for CustomResponderFut<T> {
|
||||
type Item = Response;
|
||||
type Error = T::Error;
|
||||
type Output = Result<Response, T::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut res = try_ready!(self.fut.poll());
|
||||
if let Some(status) = self.status {
|
||||
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;
|
||||
}
|
||||
if let Some(ref headers) = self.headers {
|
||||
if let Some(ref headers) = this.headers {
|
||||
for (k, v) in headers {
|
||||
res.headers_mut().insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(res))
|
||||
Poll::Ready(Ok(res))
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,8 +345,7 @@ impl<T: Responder> Future for CustomResponderFut<T> {
|
||||
/// # use futures::future::{ok, Future};
|
||||
/// use actix_web::{Either, Error, HttpResponse};
|
||||
///
|
||||
/// type RegisterResult =
|
||||
/// Either<HttpResponse, Box<dyn Future<Item = HttpResponse, Error = Error>>>;
|
||||
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
|
||||
///
|
||||
/// fn index() -> RegisterResult {
|
||||
/// if is_a_variant() {
|
||||
@ -346,9 +354,9 @@ impl<T: Responder> Future for CustomResponderFut<T> {
|
||||
/// } else {
|
||||
/// Either::B(
|
||||
/// // <- Right variant
|
||||
/// Box::new(ok(HttpResponse::Ok()
|
||||
/// Ok(HttpResponse::Ok()
|
||||
/// .content_type("text/html")
|
||||
/// .body("Hello!")))
|
||||
/// .body("Hello!"))
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
@ -369,97 +377,85 @@ where
|
||||
B: Responder,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = EitherResponder<
|
||||
<A::Future as IntoFuture>::Future,
|
||||
<B::Future as IntoFuture>::Future,
|
||||
>;
|
||||
type Future = EitherResponder<A, B>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Either::A(a) => EitherResponder::A(a.respond_to(req).into_future()),
|
||||
Either::B(b) => EitherResponder::B(b.respond_to(req).into_future()),
|
||||
Either::A(a) => EitherResponder::A(a.respond_to(req)),
|
||||
Either::B(b) => EitherResponder::B(b.respond_to(req)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub enum EitherResponder<A, B>
|
||||
where
|
||||
A: Future<Item = Response>,
|
||||
A::Error: Into<Error>,
|
||||
B: Future<Item = Response>,
|
||||
B::Error: Into<Error>,
|
||||
A: Responder,
|
||||
B: Responder,
|
||||
{
|
||||
A(A),
|
||||
B(B),
|
||||
A(#[pin] A::Future),
|
||||
B(#[pin] B::Future),
|
||||
}
|
||||
|
||||
impl<A, B> Future for EitherResponder<A, B>
|
||||
where
|
||||
A: Future<Item = Response>,
|
||||
A::Error: Into<Error>,
|
||||
B: Future<Item = Response>,
|
||||
B::Error: Into<Error>,
|
||||
A: Responder,
|
||||
B: Responder,
|
||||
{
|
||||
type Item = Response;
|
||||
type Error = Error;
|
||||
type Output = Result<Response, Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self {
|
||||
EitherResponder::A(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
|
||||
EitherResponder::B(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
|
||||
#[project]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
#[project]
|
||||
match self.project() {
|
||||
EitherResponder::A(fut) => {
|
||||
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
|
||||
}
|
||||
EitherResponder::B(fut) => {
|
||||
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Responder for Box<dyn Future<Item = I, Error = E>>
|
||||
where
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Response, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
let req = req.clone();
|
||||
Box::new(
|
||||
self.map_err(|e| e.into())
|
||||
.and_then(move |r| ResponseFuture(r.respond_to(&req).into_future())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Responder for InternalError<T>
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Result<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
let err: Error = self.into();
|
||||
Ok(err.into())
|
||||
ok(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResponseFuture<T>(T);
|
||||
#[pin_project]
|
||||
pub struct ResponseFuture<T, E> {
|
||||
#[pin]
|
||||
fut: T,
|
||||
_t: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<T> ResponseFuture<T> {
|
||||
impl<T, E> ResponseFuture<T, E> {
|
||||
pub fn new(fut: T) -> Self {
|
||||
ResponseFuture(fut)
|
||||
ResponseFuture {
|
||||
fut,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for ResponseFuture<T>
|
||||
impl<T, E> Future for ResponseFuture<T, E>
|
||||
where
|
||||
T: Future<Item = Response>,
|
||||
T::Error: Into<Error>,
|
||||
T: Future<Output = Result<Response, E>>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Item = Response;
|
||||
type Error = Error;
|
||||
type Output = Result<Response, Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
Ok(self.0.poll().map_err(|e| e.into())?)
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -476,26 +472,31 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_option_responder() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/none").to(|| -> Option<&'static str> { None }))
|
||||
.service(web::resource("/some").to(|| Some("some"))),
|
||||
);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/none").to(|| -> Option<&'static str> { None }),
|
||||
)
|
||||
.service(web::resource("/some").to(|| Some("some"))),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/none").to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
let req = TestRequest::with_uri("/none").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/some").to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
assert_eq!(bytes, Bytes::from_static(b"some"));
|
||||
let req = TestRequest::with_uri("/some").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
assert_eq!(bytes, Bytes::from_static(b"some"));
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) trait BodyTest {
|
||||
@ -526,142 +527,155 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
let resp: HttpResponse = block_on(().respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(*resp.body().body(), Body::Empty);
|
||||
let resp: HttpResponse = ().respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(*resp.body().body(), Body::Empty);
|
||||
|
||||
let resp: HttpResponse = block_on("test".respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
let resp: HttpResponse = "test".respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
|
||||
let resp: HttpResponse = block_on(b"test".respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
let resp: HttpResponse = b"test".respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
let resp: HttpResponse = block_on("test".to_string().respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
|
||||
let resp: HttpResponse =
|
||||
block_on((&"test".to_string()).respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
let resp: HttpResponse =
|
||||
(&"test".to_string()).respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
|
||||
let resp: HttpResponse =
|
||||
block_on(Bytes::from_static(b"test").respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
let resp: HttpResponse =
|
||||
Bytes::from_static(b"test").respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
let resp: HttpResponse =
|
||||
block_on(BytesMut::from(b"test".as_ref()).respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
// InternalError
|
||||
let resp: HttpResponse =
|
||||
error::InternalError::new("err", StatusCode::BAD_REQUEST)
|
||||
let resp: HttpResponse = BytesMut::from(b"test".as_ref())
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
// InternalError
|
||||
let resp: HttpResponse =
|
||||
error::InternalError::new("err", StatusCode::BAD_REQUEST)
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
// Result<I, E>
|
||||
let resp: HttpResponse =
|
||||
block_on(Ok::<_, Error>("test".to_string()).respond_to(&req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
// Result<I, E>
|
||||
let resp: HttpResponse = Ok::<_, Error>("test".to_string())
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
|
||||
let res = block_on(
|
||||
Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
|
||||
.respond_to(&req),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
let res = Err::<String, _>(error::InternalError::new(
|
||||
"err",
|
||||
StatusCode::BAD_REQUEST,
|
||||
))
|
||||
.respond_to(&req)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = block_on(
|
||||
"test"
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = "test"
|
||||
.to_string()
|
||||
.with_status(StatusCode::BAD_REQUEST)
|
||||
.respond_to(&req),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
|
||||
let res = block_on(
|
||||
"test"
|
||||
let res = "test"
|
||||
.to_string()
|
||||
.with_header("content-type", "json")
|
||||
.respond_to(&req),
|
||||
)
|
||||
.unwrap();
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("json")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("json")
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_responder_with_status_code() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res =
|
||||
block_on(("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req))
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = ("test".to_string(), StatusCode::BAD_REQUEST)
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = block_on(
|
||||
("test".to_string(), StatusCode::OK)
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = ("test".to_string(), StatusCode::OK)
|
||||
.with_header("content-type", "json")
|
||||
.respond_to(&req),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("json")
|
||||
);
|
||||
.respond_to(&req)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("json")
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
245
src/route.rs
245
src/route.rs
@ -1,9 +1,11 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::{http::Method, Error};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
|
||||
use crate::extract::FromRequest;
|
||||
use crate::guard::{self, Guard};
|
||||
@ -17,22 +19,19 @@ type BoxedRouteService<Req, Res> = Box<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = Error,
|
||||
Future = Either<
|
||||
FutureResult<Res, Error>,
|
||||
Box<dyn Future<Item = Res, Error = Error>>,
|
||||
>,
|
||||
Future = LocalBoxFuture<'static, Result<Res, Error>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
type BoxedRouteNewService<Req, Res> = Box<
|
||||
dyn NewService<
|
||||
dyn ServiceFactory<
|
||||
Config = (),
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
Service = BoxedRouteService<Req, Res>,
|
||||
Future = Box<dyn Future<Item = BoxedRouteService<Req, Res>, Error = ()>>,
|
||||
Future = LocalBoxFuture<'static, Result<BoxedRouteService<Req, Res>, ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
@ -61,7 +60,7 @@ impl Route {
|
||||
}
|
||||
}
|
||||
|
||||
impl NewService for Route {
|
||||
impl ServiceFactory for Route {
|
||||
type Config = ();
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
@ -78,26 +77,30 @@ impl NewService for Route {
|
||||
}
|
||||
}
|
||||
|
||||
type RouteFuture = Box<
|
||||
dyn Future<Item = BoxedRouteService<ServiceRequest, ServiceResponse>, Error = ()>,
|
||||
type RouteFuture = LocalBoxFuture<
|
||||
'static,
|
||||
Result<BoxedRouteService<ServiceRequest, ServiceResponse>, ()>,
|
||||
>;
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct CreateRouteService {
|
||||
#[pin]
|
||||
fut: RouteFuture,
|
||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||
}
|
||||
|
||||
impl Future for CreateRouteService {
|
||||
type Item = RouteService;
|
||||
type Error = ();
|
||||
type Output = Result<RouteService, ()>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll()? {
|
||||
Async::Ready(service) => Ok(Async::Ready(RouteService {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match this.fut.poll(cx)? {
|
||||
Poll::Ready(service) => Poll::Ready(Ok(RouteService {
|
||||
service,
|
||||
guards: self.guards.clone(),
|
||||
guards: this.guards.clone(),
|
||||
})),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,17 +125,14 @@ impl Service for RouteService {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
Box<dyn Future<Item = Self::Response, Error = Self::Error>>,
|
||||
>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
self.service.call(req)
|
||||
self.service.call(req).boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,8 +249,8 @@ impl Route {
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(info: web::Path<Info>) -> impl Future<Item = &'static str, Error = Error> {
|
||||
/// ok("Hello World!")
|
||||
/// async fn index(info: web::Path<Info>) -> Result<&'static str, Error> {
|
||||
/// Ok("Hello World!")
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -261,13 +261,13 @@ impl Route {
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_async<F, T, R>(mut self, handler: F) -> Self
|
||||
pub fn to_async<F, T, R, O, E>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
F: AsyncFactory<T, R, O, E>,
|
||||
T: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
R: Future<Output = Result<O, E>> + 'static,
|
||||
O: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new(
|
||||
handler,
|
||||
@ -278,14 +278,14 @@ impl Route {
|
||||
|
||||
struct RouteNewService<T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
|
||||
T: ServiceFactory<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
|
||||
{
|
||||
service: T,
|
||||
}
|
||||
|
||||
impl<T> RouteNewService<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -300,9 +300,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for RouteNewService<T>
|
||||
impl<T> ServiceFactory for RouteNewService<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -318,19 +318,20 @@ where
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = BoxedRouteService<ServiceRequest, Self::Response>;
|
||||
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(
|
||||
self.service
|
||||
.new_service(&())
|
||||
.map_err(|_| ())
|
||||
.and_then(|service| {
|
||||
self.service
|
||||
.new_service(&())
|
||||
.map(|result| match result {
|
||||
Ok(service) => {
|
||||
let service: BoxedRouteService<_, _> =
|
||||
Box::new(RouteServiceWrapper { service });
|
||||
Ok(service)
|
||||
}),
|
||||
)
|
||||
}
|
||||
Err(_) => Err(()),
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,25 +351,30 @@ where
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
Box<dyn Future<Item = Self::Response, Error = Self::Error>>,
|
||||
>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|(e, _)| e)
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx).map_err(|(e, _)| e)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let mut fut = self.service.call(req);
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(res)) => Either::A(ok(res)),
|
||||
Err((e, req)) => Either::A(ok(req.error_response(e))),
|
||||
Ok(Async::NotReady) => Either::B(Box::new(fut.then(|res| match res {
|
||||
// let mut fut = self.service.call(req);
|
||||
self.service
|
||||
.call(req)
|
||||
.map(|res| match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err((err, req)) => Ok(req.error_response(err)),
|
||||
}))),
|
||||
}
|
||||
})
|
||||
.boxed_local()
|
||||
|
||||
// match fut.poll() {
|
||||
// Poll::Ready(Ok(res)) => Either::Left(ok(res)),
|
||||
// Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))),
|
||||
// Poll::Pending => Either::Right(Box::new(fut.then(|res| match res {
|
||||
// Ok(res) => Ok(res),
|
||||
// Err((err, req)) => Ok(req.error_response(err)),
|
||||
// }))),
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,11 +385,11 @@ mod tests {
|
||||
use bytes::Bytes;
|
||||
use futures::Future;
|
||||
use serde_derive::Serialize;
|
||||
use tokio_timer::sleep;
|
||||
use tokio_timer::delay_for;
|
||||
|
||||
use crate::http::{Method, StatusCode};
|
||||
use crate::test::{call_service, init_service, read_body, TestRequest};
|
||||
use crate::{error, web, App, HttpResponse};
|
||||
use crate::test::{block_on, call_service, init_service, read_body, TestRequest};
|
||||
use crate::{error, web, App, Error, HttpResponse};
|
||||
|
||||
#[derive(Serialize, PartialEq, Debug)]
|
||||
struct MyObject {
|
||||
@ -392,68 +398,75 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_route() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::put().to(|| {
|
||||
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
|
||||
}))
|
||||
.route(web::post().to_async(|| {
|
||||
sleep(Duration::from_millis(100))
|
||||
.then(|_| HttpResponse::Created())
|
||||
}))
|
||||
.route(web::delete().to_async(|| {
|
||||
sleep(Duration::from_millis(100)).then(|_| {
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::put().to(|| {
|
||||
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
|
||||
})
|
||||
})),
|
||||
)
|
||||
.service(web::resource("/json").route(web::get().to_async(|| {
|
||||
sleep(Duration::from_millis(25)).then(|_| {
|
||||
Ok::<_, crate::Error>(web::Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
}))
|
||||
})
|
||||
}))),
|
||||
);
|
||||
}))
|
||||
.route(web::post().to_async(|| {
|
||||
async {
|
||||
delay_for(Duration::from_millis(100)).await;
|
||||
Ok::<_, Error>(HttpResponse::Created())
|
||||
}
|
||||
}))
|
||||
.route(web::delete().to_async(|| {
|
||||
async {
|
||||
delay_for(Duration::from_millis(100)).await;
|
||||
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
|
||||
}
|
||||
})),
|
||||
)
|
||||
.service(web::resource("/json").route(web::get().to_async(|| {
|
||||
async {
|
||||
delay_for(Duration::from_millis(25)).await;
|
||||
Ok::<_, Error>(web::Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
}))
|
||||
}
|
||||
}))),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::DELETE)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::DELETE)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::HEAD)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::HEAD)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let req = TestRequest::with_uri("/json").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let req = TestRequest::with_uri("/json").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let body = read_body(resp);
|
||||
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
|
||||
let body = read_body(resp).await;
|
||||
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
923
src/scope.rs
923
src/scope.rs
File diff suppressed because it is too large
Load Diff
@ -6,15 +6,15 @@ use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Resp
|
||||
use actix_rt::System;
|
||||
use actix_server::{Server, ServerBuilder};
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::{IntoNewService, NewService};
|
||||
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use net2::TcpBuilder;
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder};
|
||||
#[cfg(feature = "rust-tls")]
|
||||
use rustls::ServerConfig as RustlsServerConfig;
|
||||
#[cfg(feature = "openssl")]
|
||||
use open_ssl::ssl::{SslAcceptor, SslAcceptorBuilder};
|
||||
#[cfg(feature = "rustls")]
|
||||
use rust_tls::ServerConfig as RustlsServerConfig;
|
||||
|
||||
struct Socket {
|
||||
scheme: &'static str,
|
||||
@ -51,12 +51,11 @@ struct Config {
|
||||
pub struct HttpServer<F, I, S, B>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S>,
|
||||
S: NewService<Config = ServerConfig, Request = Request>,
|
||||
I: IntoServiceFactory<S>,
|
||||
S: ServiceFactory<Config = ServerConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
B: MessageBody,
|
||||
{
|
||||
pub(super) factory: F,
|
||||
@ -71,12 +70,12 @@ where
|
||||
impl<F, I, S, B> HttpServer<F, I, S, B>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S>,
|
||||
S: NewService<Config = ServerConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
I: IntoServiceFactory<S>,
|
||||
S: ServiceFactory<Config = ServerConfig, Request = Request>,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create new http server with application factory
|
||||
@ -254,11 +253,11 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn listen_ssl(
|
||||
pub fn listen_openssl(
|
||||
self,
|
||||
lst: net::TcpListener,
|
||||
builder: SslAcceptorBuilder,
|
||||
@ -266,13 +265,14 @@ where
|
||||
self.listen_ssl_inner(lst, openssl_acceptor(builder)?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
fn listen_ssl_inner(
|
||||
mut self,
|
||||
lst: net::TcpListener,
|
||||
acceptor: SslAcceptor,
|
||||
) -> io::Result<Self> {
|
||||
use actix_server::ssl::{OpensslAcceptor, SslError};
|
||||
use actix_service::pipeline_factory;
|
||||
|
||||
let acceptor = OpensslAcceptor::new(acceptor);
|
||||
let factory = self.factory.clone();
|
||||
@ -288,7 +288,7 @@ where
|
||||
lst,
|
||||
move || {
|
||||
let c = cfg.lock();
|
||||
acceptor.clone().map_err(SslError::Ssl).and_then(
|
||||
pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then(
|
||||
HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
@ -302,7 +302,7 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
#[cfg(feature = "rustls")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
@ -314,13 +314,14 @@ where
|
||||
self.listen_rustls_inner(lst, config)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
#[cfg(feature = "rustls")]
|
||||
fn listen_rustls_inner(
|
||||
mut self,
|
||||
lst: net::TcpListener,
|
||||
mut config: RustlsServerConfig,
|
||||
) -> io::Result<Self> {
|
||||
use actix_server::ssl::{RustlsAcceptor, SslError};
|
||||
use actix_service::pipeline_factory;
|
||||
|
||||
let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()];
|
||||
config.set_protocols(&protos);
|
||||
@ -339,7 +340,7 @@ where
|
||||
lst,
|
||||
move || {
|
||||
let c = cfg.lock();
|
||||
acceptor.clone().map_err(SslError::Ssl).and_then(
|
||||
pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then(
|
||||
HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
@ -397,11 +398,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn bind_ssl<A>(
|
||||
pub fn bind_openssl<A>(
|
||||
mut self,
|
||||
addr: A,
|
||||
builder: SslAcceptorBuilder,
|
||||
@ -419,7 +420,7 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
#[cfg(feature = "rustls")]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
@ -435,7 +436,7 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "uds")]
|
||||
#[cfg(unix)]
|
||||
/// Start listening for unix domain connections on existing listener.
|
||||
///
|
||||
/// This method is available with `uds` feature.
|
||||
@ -466,7 +467,7 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "uds")]
|
||||
#[cfg(unix)]
|
||||
/// Start listening for incoming unix domain connections.
|
||||
///
|
||||
/// This method is available with `uds` feature.
|
||||
@ -502,8 +503,8 @@ where
|
||||
impl<F, I, S, B> HttpServer<F, I, S, B>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S>,
|
||||
S: NewService<Config = ServerConfig, Request = Request>,
|
||||
I: IntoServiceFactory<S>,
|
||||
S: ServiceFactory<Config = ServerConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
@ -577,10 +578,10 @@ fn create_tcp_listener(
|
||||
Ok(builder.listen(backlog)?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Configure `SslAcceptorBuilder` with custom server flags.
|
||||
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
|
||||
use openssl::ssl::AlpnError;
|
||||
use open_ssl::ssl::AlpnError;
|
||||
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
|
@ -9,8 +9,8 @@ use actix_http::{
|
||||
ResponseHead,
|
||||
};
|
||||
use actix_router::{Path, Resource, ResourceDef, Url};
|
||||
use actix_service::{IntoNewService, NewService};
|
||||
use futures::future::{ok, FutureResult, IntoFuture};
|
||||
use actix_service::{IntoServiceFactory, ServiceFactory};
|
||||
use futures::future::{ok, Ready};
|
||||
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::Data;
|
||||
@ -24,7 +24,7 @@ pub trait HttpServiceFactory {
|
||||
fn register(self, config: &mut AppService);
|
||||
}
|
||||
|
||||
pub(crate) trait ServiceFactory {
|
||||
pub(crate) trait AppServiceFactory {
|
||||
fn register(&mut self, config: &mut AppService);
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ impl<T> ServiceFactoryWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ServiceFactory for ServiceFactoryWrapper<T>
|
||||
impl<T> AppServiceFactory for ServiceFactoryWrapper<T>
|
||||
where
|
||||
T: HttpServiceFactory,
|
||||
{
|
||||
@ -404,16 +404,6 @@ impl<B> Into<Response<B>> for ServiceResponse<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> IntoFuture for ServiceResponse<B> {
|
||||
type Item = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Future = FutureResult<ServiceResponse<B>, Error>;
|
||||
|
||||
fn into_future(self) -> Self::Future {
|
||||
ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: MessageBody> fmt::Debug for ServiceResponse<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(
|
||||
@ -459,10 +449,11 @@ impl WebService {
|
||||
/// Add match guard to a web service.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, guard, dev, App, HttpResponse};
|
||||
/// use futures::future::{ok, Ready};
|
||||
/// use actix_web::{web, guard, dev, App, Error, HttpResponse};
|
||||
///
|
||||
/// fn index(req: dev::ServiceRequest) -> dev::ServiceResponse {
|
||||
/// req.into_response(HttpResponse::Ok().finish())
|
||||
/// fn index(req: dev::ServiceRequest) -> Ready<Result<dev::ServiceResponse, Error>> {
|
||||
/// ok(req.into_response(HttpResponse::Ok().finish()))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -482,8 +473,8 @@ impl WebService {
|
||||
/// Set a service factory implementation and generate web service.
|
||||
pub fn finish<T, F>(self, service: F) -> impl HttpServiceFactory
|
||||
where
|
||||
F: IntoNewService<T>,
|
||||
T: NewService<
|
||||
F: IntoServiceFactory<T>,
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -492,7 +483,7 @@ impl WebService {
|
||||
> + 'static,
|
||||
{
|
||||
WebServiceImpl {
|
||||
srv: service.into_new_service(),
|
||||
srv: service.into_factory(),
|
||||
rdef: self.rdef,
|
||||
name: self.name,
|
||||
guards: self.guards,
|
||||
@ -509,7 +500,7 @@ struct WebServiceImpl<T> {
|
||||
|
||||
impl<T> HttpServiceFactory for WebServiceImpl<T>
|
||||
where
|
||||
T: NewService<
|
||||
T: ServiceFactory<
|
||||
Config = (),
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
@ -539,8 +530,9 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::test::{block_on, init_service, TestRequest};
|
||||
use crate::{guard, http, web, App, HttpResponse};
|
||||
use actix_service::Service;
|
||||
|
||||
#[test]
|
||||
fn test_service_request() {
|
||||
@ -565,25 +557,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_service() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::service("/test").name("test").finish(
|
||||
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
|
||||
)),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||
block_on(async {
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::service("/test").name("test").finish(
|
||||
|req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::Ok().finish()))
|
||||
},
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::service("/test").guard(guard::Get()).finish(
|
||||
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
|
||||
)),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(http::Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::service("/test").guard(guard::Get()).finish(
|
||||
|req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::Ok().finish()))
|
||||
},
|
||||
),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(http::Method::PUT)
|
||||
.to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
420
src/test.rs
420
src/test.rs
@ -7,10 +7,10 @@ use actix_http::test::TestRequest as HttpTestRequest;
|
||||
use actix_http::{cookie::Cookie, Extensions, Request};
|
||||
use actix_router::{Path, ResourceDef, Url};
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::{IntoNewService, IntoService, NewService, Service};
|
||||
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{ok, Future};
|
||||
use futures::Stream;
|
||||
use futures::future::{ok, Future, FutureExt};
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
@ -39,7 +39,7 @@ pub fn default_service(
|
||||
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
|
||||
{
|
||||
(move |req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::build(status_code).finish())
|
||||
ok(req.into_response(HttpResponse::build(status_code).finish()))
|
||||
})
|
||||
.into_service()
|
||||
}
|
||||
@ -66,12 +66,12 @@ pub fn default_service(
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn init_service<R, S, B, E>(
|
||||
pub async fn init_service<R, S, B, E>(
|
||||
app: R,
|
||||
) -> impl Service<Request = Request, Response = ServiceResponse<B>, Error = E>
|
||||
where
|
||||
R: IntoNewService<S>,
|
||||
S: NewService<
|
||||
R: IntoServiceFactory<S>,
|
||||
S: ServiceFactory<
|
||||
Config = ServerConfig,
|
||||
Request = Request,
|
||||
Response = ServiceResponse<B>,
|
||||
@ -80,9 +80,8 @@ where
|
||||
S::InitError: std::fmt::Debug,
|
||||
{
|
||||
let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap());
|
||||
let srv = app.into_new_service();
|
||||
let fut = run_on(move || srv.new_service(&cfg));
|
||||
block_on(fut).unwrap()
|
||||
let srv = app.into_factory();
|
||||
srv.new_service(&cfg).await.unwrap()
|
||||
}
|
||||
|
||||
/// Calls service and waits for response future completion.
|
||||
@ -106,12 +105,12 @@ where
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
|
||||
pub async fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
|
||||
where
|
||||
S: Service<Request = R, Response = ServiceResponse<B>, Error = E>,
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
block_on(run_on(move || app.call(req))).unwrap()
|
||||
app.call(req).await.unwrap()
|
||||
}
|
||||
|
||||
/// Helper function that returns a response body of a TestRequest
|
||||
@ -138,22 +137,22 @@ where
|
||||
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
|
||||
pub async fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
|
||||
where
|
||||
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
|
||||
resp.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.map(|body: BytesMut| body.freeze())
|
||||
})
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
|
||||
let mut resp = app
|
||||
.call(req)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"));
|
||||
|
||||
let mut body = resp.take_body();
|
||||
let mut bytes = BytesMut::new();
|
||||
while let Some(item) = body.next().await {
|
||||
bytes.extend_from_slice(&item.unwrap());
|
||||
}
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
/// Helper function that returns a response body of a ServiceResponse.
|
||||
@ -181,19 +180,27 @@ where
|
||||
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
|
||||
pub async fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
res.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.map(|body: BytesMut| body.freeze())
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
|
||||
let mut body = res.take_body();
|
||||
let mut bytes = BytesMut::new();
|
||||
while let Some(item) = body.next().await {
|
||||
bytes.extend_from_slice(&item.unwrap());
|
||||
}
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, Error>> + Unpin,
|
||||
{
|
||||
let mut data = BytesMut::new();
|
||||
while let Some(item) = stream.next().await {
|
||||
data.extend_from_slice(&item?);
|
||||
}
|
||||
Ok(data.freeze())
|
||||
}
|
||||
|
||||
/// Helper function that returns a deserialized response body of a TestRequest
|
||||
@ -230,27 +237,16 @@ where
|
||||
/// let result: Person = test::read_response_json(&mut app, req);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
|
||||
pub async fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
|
||||
where
|
||||
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
|
||||
resp.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.and_then(|body: BytesMut| {
|
||||
ok(serde_json::from_slice(&body).unwrap_or_else(|_| {
|
||||
panic!("read_response_json failed during deserialization")
|
||||
}))
|
||||
})
|
||||
})
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
|
||||
let body = read_response(app, req).await;
|
||||
|
||||
serde_json::from_slice(&body)
|
||||
.unwrap_or_else(|_| panic!("read_response_json failed during deserialization"))
|
||||
}
|
||||
|
||||
/// Test `Request` builder.
|
||||
@ -511,74 +507,82 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
let req = TestRequest::with_hdr(header::ContentType::json())
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.param("test", "123")
|
||||
.data(10u32)
|
||||
.to_http_request();
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
assert!(req.headers().contains_key(header::DATE));
|
||||
assert_eq!(&req.match_info()["test"], "123");
|
||||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
let data = req.get_app_data::<u32>().unwrap();
|
||||
assert!(req.get_app_data::<u64>().is_none());
|
||||
assert_eq!(*data, 10);
|
||||
assert_eq!(*data.get_ref(), 10);
|
||||
block_on(async {
|
||||
let req = TestRequest::with_hdr(header::ContentType::json())
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.param("test", "123")
|
||||
.data(10u32)
|
||||
.to_http_request();
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
assert!(req.headers().contains_key(header::DATE));
|
||||
assert_eq!(&req.match_info()["test"], "123");
|
||||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
let data = req.get_app_data::<u32>().unwrap();
|
||||
assert!(req.get_app_data::<u64>().is_none());
|
||||
assert_eq!(*data, 10);
|
||||
assert_eq!(*data.get_ref(), 10);
|
||||
|
||||
assert!(req.app_data::<u64>().is_none());
|
||||
let data = req.app_data::<u32>().unwrap();
|
||||
assert_eq!(*data, 10);
|
||||
assert!(req.app_data::<u64>().is_none());
|
||||
let data = req.app_data::<u32>().unwrap();
|
||||
assert_eq!(*data, 10);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_methods() {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
|
||||
.route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
|
||||
.route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
|
||||
),
|
||||
);
|
||||
block_on(async {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
|
||||
.route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
|
||||
.route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let put_req = TestRequest::put()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
let put_req = TestRequest::put()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, put_req);
|
||||
assert_eq!(result, Bytes::from_static(b"put!"));
|
||||
let result = read_response(&mut app, put_req).await;
|
||||
assert_eq!(result, Bytes::from_static(b"put!"));
|
||||
|
||||
let patch_req = TestRequest::patch()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
let patch_req = TestRequest::patch()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, patch_req);
|
||||
assert_eq!(result, Bytes::from_static(b"patch!"));
|
||||
let result = read_response(&mut app, patch_req).await;
|
||||
assert_eq!(result, Bytes::from_static(b"patch!"));
|
||||
|
||||
let delete_req = TestRequest::delete().uri("/index.html").to_request();
|
||||
let result = read_response(&mut app, delete_req);
|
||||
assert_eq!(result, Bytes::from_static(b"delete!"));
|
||||
let delete_req = TestRequest::delete().uri("/index.html").to_request();
|
||||
let result = read_response(&mut app, delete_req).await;
|
||||
assert_eq!(result, Bytes::from_static(b"delete!"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response() {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
|
||||
),
|
||||
);
|
||||
block_on(async {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
let req = TestRequest::post()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, req);
|
||||
assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
let result = read_response(&mut app, req).await;
|
||||
assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -589,129 +593,147 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_response_json() {
|
||||
let mut app = init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Json<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)));
|
||||
block_on(async {
|
||||
let mut app =
|
||||
init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Json<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
|
||||
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.set_payload(payload)
|
||||
.to_request();
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.set_payload(payload)
|
||||
.to_request();
|
||||
|
||||
let result: Person = read_response_json(&mut app, req);
|
||||
assert_eq!(&result.id, "12345");
|
||||
let result: Person = read_response_json(&mut app, req).await;
|
||||
assert_eq!(&result.id, "12345");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_response_form() {
|
||||
let mut app = init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Form<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)));
|
||||
block_on(async {
|
||||
let mut app =
|
||||
init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Form<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let payload = Person {
|
||||
id: "12345".to_string(),
|
||||
name: "User name".to_string(),
|
||||
};
|
||||
let payload = Person {
|
||||
id: "12345".to_string(),
|
||||
name: "User name".to_string(),
|
||||
};
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
|
||||
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
|
||||
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
|
||||
|
||||
let result: Person = read_response_json(&mut app, req);
|
||||
assert_eq!(&result.id, "12345");
|
||||
assert_eq!(&result.name, "User name");
|
||||
let result: Person = read_response_json(&mut app, req).await;
|
||||
assert_eq!(&result.id, "12345");
|
||||
assert_eq!(&result.name, "User name");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_response_json() {
|
||||
let mut app = init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Json<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)));
|
||||
block_on(async {
|
||||
let mut app =
|
||||
init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Json<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let payload = Person {
|
||||
id: "12345".to_string(),
|
||||
name: "User name".to_string(),
|
||||
};
|
||||
let payload = Person {
|
||||
id: "12345".to_string(),
|
||||
name: "User name".to_string(),
|
||||
};
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.set_json(&payload)
|
||||
.to_request();
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.set_json(&payload)
|
||||
.to_request();
|
||||
|
||||
assert_eq!(req.content_type(), "application/json");
|
||||
assert_eq!(req.content_type(), "application/json");
|
||||
|
||||
let result: Person = read_response_json(&mut app, req);
|
||||
assert_eq!(&result.id, "12345");
|
||||
assert_eq!(&result.name, "User name");
|
||||
let result: Person = read_response_json(&mut app, req).await;
|
||||
assert_eq!(&result.id, "12345");
|
||||
assert_eq!(&result.name, "User name");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_with_block() {
|
||||
fn async_with_block() -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
web::block(move || Some(4).ok_or("wrong")).then(|res| match res {
|
||||
Ok(value) => HttpResponse::Ok()
|
||||
.content_type("text/plain")
|
||||
.body(format!("Async with block value: {}", value)),
|
||||
Err(_) => panic!("Unexpected"),
|
||||
})
|
||||
}
|
||||
block_on(async {
|
||||
async fn async_with_block() -> Result<HttpResponse, Error> {
|
||||
let res = web::block(move || Some(4usize).ok_or("wrong")).await;
|
||||
|
||||
let mut app = init_service(
|
||||
App::new().service(web::resource("/index.html").to_async(async_with_block)),
|
||||
);
|
||||
|
||||
let req = TestRequest::post().uri("/index.html").to_request();
|
||||
let res = block_fn(|| app.call(req)).unwrap();
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_actor() {
|
||||
use actix::Actor;
|
||||
|
||||
struct MyActor;
|
||||
|
||||
struct Num(usize);
|
||||
impl actix::Message for Num {
|
||||
type Result = usize;
|
||||
}
|
||||
impl actix::Actor for MyActor {
|
||||
type Context = actix::Context<Self>;
|
||||
}
|
||||
impl actix::Handler<Num> for MyActor {
|
||||
type Result = usize;
|
||||
fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result {
|
||||
msg.0
|
||||
match res? {
|
||||
Ok(value) => Ok(HttpResponse::Ok()
|
||||
.content_type("text/plain")
|
||||
.body(format!("Async with block value: {}", value))),
|
||||
Err(_) => panic!("Unexpected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let addr = run_on(|| MyActor.start());
|
||||
let mut app = init_service(App::new().service(
|
||||
web::resource("/index.html").to_async(move || {
|
||||
addr.send(Num(1)).from_err().and_then(|res| {
|
||||
if res == 1 {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
})
|
||||
}),
|
||||
));
|
||||
let mut app = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/index.html").to_async(async_with_block)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::post().uri("/index.html").to_request();
|
||||
let res = block_fn(|| app.call(req)).unwrap();
|
||||
assert!(res.status().is_success());
|
||||
let req = TestRequest::post().uri("/index.html").to_request();
|
||||
let res = app.call(req).await.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
})
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_actor() {
|
||||
// use actix::Actor;
|
||||
|
||||
// struct MyActor;
|
||||
|
||||
// struct Num(usize);
|
||||
// impl actix::Message for Num {
|
||||
// type Result = usize;
|
||||
// }
|
||||
// impl actix::Actor for MyActor {
|
||||
// type Context = actix::Context<Self>;
|
||||
// }
|
||||
// impl actix::Handler<Num> for MyActor {
|
||||
// type Result = usize;
|
||||
// fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result {
|
||||
// msg.0
|
||||
// }
|
||||
// }
|
||||
|
||||
// let addr = run_on(|| MyActor.start());
|
||||
// let mut app = init_service(App::new().service(
|
||||
// web::resource("/index.html").to_async(move || {
|
||||
// addr.send(Num(1)).from_err().and_then(|res| {
|
||||
// if res == 1 {
|
||||
// HttpResponse::Ok()
|
||||
// } else {
|
||||
// HttpResponse::BadRequest()
|
||||
// }
|
||||
// })
|
||||
// }),
|
||||
// ));
|
||||
|
||||
// let req = TestRequest::post().uri("/index.html").to_request();
|
||||
// let res = block_fn(|| app.call(req)).unwrap();
|
||||
// assert!(res.status().is_success());
|
||||
// }
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
//! Form extractor
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, ops};
|
||||
|
||||
use actix_http::{Error, HttpMessage, Payload, Response};
|
||||
use bytes::BytesMut;
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures::{Future, Poll, Stream};
|
||||
use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready};
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
@ -110,7 +114,7 @@ where
|
||||
{
|
||||
type Config = FormConfig;
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Self, Error = Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
@ -120,18 +124,19 @@ where
|
||||
.map(|c| (c.limit, c.ehandler.clone()))
|
||||
.unwrap_or((16384, None));
|
||||
|
||||
Box::new(
|
||||
UrlEncoded::new(req, payload)
|
||||
.limit(limit)
|
||||
.map_err(move |e| {
|
||||
UrlEncoded::new(req, payload)
|
||||
.limit(limit)
|
||||
.map(move |res| match res {
|
||||
Err(e) => {
|
||||
if let Some(err) = err {
|
||||
(*err)(e, &req2)
|
||||
Err((*err)(e, &req2))
|
||||
} else {
|
||||
e.into()
|
||||
Err(e.into())
|
||||
}
|
||||
})
|
||||
.map(Form),
|
||||
)
|
||||
}
|
||||
Ok(item) => Ok(Form(item)),
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,15 +154,15 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||
|
||||
impl<T: Serialize> Responder for Form<T> {
|
||||
type Error = Error;
|
||||
type Future = Result<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
let body = match serde_urlencoded::to_string(&self.0) {
|
||||
Ok(body) => body,
|
||||
Err(e) => return Err(e.into()),
|
||||
Err(e) => return err(e.into()),
|
||||
};
|
||||
|
||||
Ok(Response::build(StatusCode::OK)
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.set(ContentType::form_url_encoded())
|
||||
.body(body))
|
||||
}
|
||||
@ -240,7 +245,7 @@ pub struct UrlEncoded<U> {
|
||||
length: Option<usize>,
|
||||
encoding: &'static Encoding,
|
||||
err: Option<UrlencodedError>,
|
||||
fut: Option<Box<dyn Future<Item = U, Error = UrlencodedError>>>,
|
||||
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<U> UrlEncoded<U> {
|
||||
@ -301,45 +306,45 @@ impl<U> Future for UrlEncoded<U>
|
||||
where
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = UrlencodedError;
|
||||
type Output = Result<U, UrlencodedError>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
return Pin::new(fut).poll(cx);
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
|
||||
// payload size
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(UrlencodedError::Overflow { size: len, limit });
|
||||
return Poll::Ready(Err(UrlencodedError::Overflow { size: len, limit }));
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let encoding = self.encoding;
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(UrlencodedError::Overflow {
|
||||
size: body.len() + chunk.len(),
|
||||
limit,
|
||||
})
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
let mut stream = self.stream.take().unwrap();
|
||||
|
||||
self.fut = Some(
|
||||
async move {
|
||||
let mut body = BytesMut::with_capacity(8192);
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item?;
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
return Err(UrlencodedError::Overflow {
|
||||
size: body.len() + chunk.len(),
|
||||
limit,
|
||||
});
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
}
|
||||
}
|
||||
})
|
||||
.and_then(move |body| {
|
||||
|
||||
if encoding == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
@ -351,9 +356,10 @@ where
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
});
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
}
|
||||
.boxed_local(),
|
||||
);
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,20 +380,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_form() {
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
|
||||
let Form(s) = block_on(Form::<Info>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
Info {
|
||||
hello: "world".into(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
let Form(s) = Form::<Info>::from_request(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
Info {
|
||||
hello: "world".into(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
|
||||
@ -410,81 +420,93 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded_error() {
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "xxxx")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
||||
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "1000000")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(
|
||||
info.err().unwrap(),
|
||||
UrlencodedError::Overflow { size: 0, limit: 0 }
|
||||
));
|
||||
|
||||
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
||||
.header(CONTENT_LENGTH, "10")
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "xxxx")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
||||
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "1000000")
|
||||
.to_http_parts();
|
||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||
assert!(eq(
|
||||
info.err().unwrap(),
|
||||
UrlencodedError::Overflow { size: 0, limit: 0 }
|
||||
));
|
||||
|
||||
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
||||
.header(CONTENT_LENGTH, "10")
|
||||
.to_http_parts();
|
||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded() {
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
hello: "world".to_owned(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
hello: "world".to_owned(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded; charset=utf-8",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded; charset=utf-8",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
hello: "world".to_owned(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
hello: "world".to_owned(),
|
||||
counter: 123
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
let form = Form(Info {
|
||||
hello: "world".to_string(),
|
||||
counter: 123,
|
||||
});
|
||||
let resp = form.respond_to(&req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
);
|
||||
let form = Form(Info {
|
||||
hello: "world".to_string(),
|
||||
counter: 123,
|
||||
});
|
||||
let resp = form.respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
);
|
||||
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
//! Json extractor/responder
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, ops};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready};
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
@ -118,15 +122,15 @@ where
|
||||
|
||||
impl<T: Serialize> Responder for Json<T> {
|
||||
type Error = Error;
|
||||
type Future = Result<Response, Error>;
|
||||
type Future = Ready<Result<Response, Error>>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
let body = match serde_json::to_string(&self.0) {
|
||||
Ok(body) => body,
|
||||
Err(e) => return Err(e.into()),
|
||||
Err(e) => return err(e.into()),
|
||||
};
|
||||
|
||||
Ok(Response::build(StatusCode::OK)
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
@ -169,7 +173,7 @@ where
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Box<dyn Future<Item = Self, Error = Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self, Error>>;
|
||||
type Config = JsonConfig;
|
||||
|
||||
#[inline]
|
||||
@ -180,23 +184,24 @@ where
|
||||
.map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone()))
|
||||
.unwrap_or((32768, None, None));
|
||||
|
||||
Box::new(
|
||||
JsonBody::new(req, payload, ctype)
|
||||
.limit(limit)
|
||||
.map_err(move |e| {
|
||||
JsonBody::new(req, payload, ctype)
|
||||
.limit(limit)
|
||||
.map(move |res| match res {
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Failed to deserialize Json from payload. \
|
||||
Request path: {}",
|
||||
req2.path()
|
||||
);
|
||||
if let Some(err) = err {
|
||||
(*err)(e, &req2)
|
||||
Err((*err)(e, &req2))
|
||||
} else {
|
||||
e.into()
|
||||
Err(e.into())
|
||||
}
|
||||
})
|
||||
.map(Json),
|
||||
)
|
||||
}
|
||||
Ok(data) => Ok(Json(data)),
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,7 +295,7 @@ pub struct JsonBody<U> {
|
||||
length: Option<usize>,
|
||||
stream: Option<Decompress<Payload>>,
|
||||
err: Option<JsonPayloadError>,
|
||||
fut: Option<Box<dyn Future<Item = U, Error = JsonPayloadError>>>,
|
||||
fut: Option<LocalBoxFuture<'static, Result<U, JsonPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<U> JsonBody<U>
|
||||
@ -349,41 +354,43 @@ impl<U> Future for JsonBody<U>
|
||||
where
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = JsonPayloadError;
|
||||
type Output = Result<U, JsonPayloadError>;
|
||||
|
||||
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
return Pin::new(fut).poll(cx);
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(JsonPayloadError::Overflow);
|
||||
return Poll::Ready(Err(JsonPayloadError::Overflow));
|
||||
}
|
||||
}
|
||||
let mut stream = self.stream.take().unwrap();
|
||||
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(JsonPayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
self.fut = Some(
|
||||
async move {
|
||||
let mut body = BytesMut::with_capacity(8192);
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item?;
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
return Err(JsonPayloadError::Overflow);
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
}
|
||||
}
|
||||
})
|
||||
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
Ok(serde_json::from_slice::<U>(&body)?)
|
||||
}
|
||||
.boxed_local(),
|
||||
);
|
||||
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +402,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::error::InternalError;
|
||||
use crate::http::header;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
use crate::test::{block_on, load_stream, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
@ -419,218 +426,234 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
block_on(async {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
let j = Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
});
|
||||
let resp = j.respond_to(&req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
header::HeaderValue::from_static("application/json")
|
||||
);
|
||||
let j = Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
});
|
||||
let resp = j.respond_to(&req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
header::HeaderValue::from_static("application/json")
|
||||
);
|
||||
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_error_responder() {
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(10).error_handler(|err, _| {
|
||||
let msg = MyObject {
|
||||
name: "invalid request".to_string(),
|
||||
};
|
||||
let resp = HttpResponse::BadRequest()
|
||||
.body(serde_json::to_string(&msg).unwrap());
|
||||
InternalError::from_response(err, resp).into()
|
||||
}))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(10).error_handler(|err, _| {
|
||||
let msg = MyObject {
|
||||
name: "invalid request".to_string(),
|
||||
};
|
||||
let resp = HttpResponse::BadRequest()
|
||||
.body(serde_json::to_string(&msg).unwrap());
|
||||
InternalError::from_response(err, resp).into()
|
||||
}))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
let mut resp = Response::from_error(s.err().unwrap().into());
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
let mut resp = Response::from_error(s.err().unwrap().into());
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let body = block_on(resp.take_body().concat2()).unwrap();
|
||||
let msg: MyObject = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(msg.name, "invalid request");
|
||||
let body = load_stream(resp.take_body()).await.unwrap();
|
||||
let msg: MyObject = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(msg.name, "invalid request");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract() {
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(s.name, "test");
|
||||
assert_eq!(
|
||||
s.into_inner(),
|
||||
MyObject {
|
||||
name: "test".to_string()
|
||||
}
|
||||
);
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(s.name, "test");
|
||||
assert_eq!(
|
||||
s.into_inner(),
|
||||
MyObject {
|
||||
name: "test".to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(10))
|
||||
.to_http_parts();
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(10))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(format!("{}", s.err().unwrap())
|
||||
.contains("Json payload size is bigger than allowed"));
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
assert!(format!("{}", s.err().unwrap())
|
||||
.contains("Json payload size is bigger than allowed"));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(
|
||||
JsonConfig::default()
|
||||
.limit(10)
|
||||
.error_handler(|_, _| JsonPayloadError::ContentType.into()),
|
||||
)
|
||||
.to_http_parts();
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(
|
||||
JsonConfig::default()
|
||||
.limit(10)
|
||||
.error_handler(|_, _| JsonPayloadError::ContentType.into()),
|
||||
)
|
||||
.to_http_parts();
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_body() {
|
||||
let (req, mut pl) = TestRequest::default().to_http_parts();
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::default().to_http_parts();
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
)
|
||||
.to_http_parts();
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
)
|
||||
.to_http_parts();
|
||||
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
||||
.limit(100)
|
||||
.await;
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_http_parts();
|
||||
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
||||
assert_eq!(
|
||||
json.ok().unwrap(),
|
||||
MyObject {
|
||||
name: "test".to_owned()
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_bad_content_type() {
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
)
|
||||
.to_http_parts();
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
)
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None).limit(100));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(4096))
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
|
||||
assert_eq!(
|
||||
json.ok().unwrap(),
|
||||
MyObject {
|
||||
name: "test".to_owned()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_bad_content_type() {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().limit(4096))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(s.is_err())
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
assert!(s.is_err())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_good_custom_content_type() {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
}))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
}))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(s.is_ok())
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
assert!(s.is_ok())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_bad_custom_content_type() {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/html"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
}))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/html"),
|
||||
)
|
||||
.header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
}))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(s.is_err())
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
assert!(s.is_err())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use std::{fmt, ops};
|
||||
|
||||
use actix_http::error::{Error, ErrorNotFound};
|
||||
use actix_router::PathDeserializer;
|
||||
use futures::future::{ready, Ready};
|
||||
use serde::de;
|
||||
|
||||
use crate::dev::Payload;
|
||||
@ -159,7 +160,7 @@ where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
type Config = PathConfig;
|
||||
|
||||
#[inline]
|
||||
@ -169,21 +170,23 @@ where
|
||||
.map(|c| c.ehandler.clone())
|
||||
.unwrap_or(None);
|
||||
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
.map_err(move |e| {
|
||||
log::debug!(
|
||||
"Failed during Path extractor deserialization. \
|
||||
Request path: {:?}",
|
||||
req.path()
|
||||
);
|
||||
if let Some(error_handler) = error_handler {
|
||||
let e = PathError::Deserialize(e);
|
||||
(error_handler)(e, req)
|
||||
} else {
|
||||
ErrorNotFound(e)
|
||||
}
|
||||
})
|
||||
ready(
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
.map_err(move |e| {
|
||||
log::debug!(
|
||||
"Failed during Path extractor deserialization. \
|
||||
Request path: {:?}",
|
||||
req.path()
|
||||
);
|
||||
if let Some(error_handler) = error_handler {
|
||||
let e = PathError::Deserialize(e);
|
||||
(error_handler)(e, req)
|
||||
} else {
|
||||
ErrorNotFound(e)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,100 +271,116 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_single() {
|
||||
let resource = ResourceDef::new("/{value}/");
|
||||
block_on(async {
|
||||
let resource = ResourceDef::new("/{value}/");
|
||||
|
||||
let mut req = TestRequest::with_uri("/32/").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
let mut req = TestRequest::with_uri("/32/").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32);
|
||||
assert!(Path::<MyStruct>::from_request(&req, &mut pl).is_err());
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32);
|
||||
assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_extract() {
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
block_on(async {
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let res =
|
||||
block_on(<(Path<(String, String)>,)>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
|
||||
let res = block_on(
|
||||
<(Path<(String, String)>, Path<(String, String)>)>::from_request(
|
||||
let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request(
|
||||
&req, &mut pl,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
assert_eq!((res.1).0, "name");
|
||||
assert_eq!((res.1).1, "user1");
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
assert_eq!((res.1).0, "name");
|
||||
assert_eq!((res.1).1, "user1");
|
||||
|
||||
let () = <()>::from_request(&req, &mut pl).unwrap();
|
||||
let () = <()>::from_request(&req, &mut pl).await.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
block_on(async {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
s.value = "user2".to_string();
|
||||
assert_eq!(s.value, "user2");
|
||||
assert_eq!(
|
||||
format!("{}, {:?}", s, s),
|
||||
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
|
||||
);
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.value, "user2");
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
s.value = "user2".to_string();
|
||||
assert_eq!(s.value, "user2");
|
||||
assert_eq!(
|
||||
format!("{}, {:?}", s, s),
|
||||
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
|
||||
);
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.value, "user2");
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
let res = Path::<Vec<String>>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_err_handler() {
|
||||
let (req, mut pl) = TestRequest::with_uri("/name/user1/")
|
||||
.data(PathConfig::default().error_handler(|err, _| {
|
||||
error::InternalError::from_response(
|
||||
err,
|
||||
HttpResponse::Conflict().finish(),
|
||||
)
|
||||
.into()
|
||||
}))
|
||||
.to_http_parts();
|
||||
block_on(async {
|
||||
let (req, mut pl) = TestRequest::with_uri("/name/user1/")
|
||||
.data(PathConfig::default().error_handler(|err, _| {
|
||||
error::InternalError::from_response(
|
||||
err,
|
||||
HttpResponse::Conflict().finish(),
|
||||
)
|
||||
.into()
|
||||
}))
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Path::<(usize,)>::from_request(&req, &mut pl)).unwrap_err();
|
||||
let res: HttpResponse = s.into();
|
||||
let s = Path::<(usize,)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let res: HttpResponse = s.into();
|
||||
|
||||
assert_eq!(res.status(), http::StatusCode::CONFLICT);
|
||||
assert_eq!(res.status(), http::StatusCode::CONFLICT);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
//! Payload/Bytes/String 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 actix_http::HttpMessage;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding_rs::UTF_8;
|
||||
use futures::future::{err, Either, FutureResult};
|
||||
use futures::{Future, Poll, Stream};
|
||||
use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
use futures::{Stream, StreamExt};
|
||||
use mime::Mime;
|
||||
|
||||
use crate::dev;
|
||||
@ -19,21 +22,19 @@ use crate::request::HttpRequest;
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use futures::{Future, Stream};
|
||||
/// use futures::{Future, Stream, StreamExt};
|
||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
||||
///
|
||||
/// /// extract binary data from request
|
||||
/// fn index(body: web::Payload) -> impl Future<Item = HttpResponse, Error = Error>
|
||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
||||
/// {
|
||||
/// body.map_err(Error::from)
|
||||
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
|
||||
/// body.extend_from_slice(&chunk);
|
||||
/// Ok::<_, Error>(body)
|
||||
/// })
|
||||
/// .and_then(|body| {
|
||||
/// format!("Body {:?}!", body);
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// })
|
||||
/// 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() {
|
||||
@ -53,12 +54,14 @@ impl Payload {
|
||||
}
|
||||
|
||||
impl Stream for Payload {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
type Item = Result<Bytes, PayloadError>;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, PayloadError> {
|
||||
self.0.poll()
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
Pin::new(&mut self.0).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,21 +70,19 @@ impl Stream for Payload {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use futures::{Future, Stream};
|
||||
/// use futures::{Future, Stream, StreamExt};
|
||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
||||
///
|
||||
/// /// extract binary data from request
|
||||
/// fn index(body: web::Payload) -> impl Future<Item = HttpResponse, Error = Error>
|
||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
||||
/// {
|
||||
/// body.map_err(Error::from)
|
||||
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
|
||||
/// body.extend_from_slice(&chunk);
|
||||
/// Ok::<_, Error>(body)
|
||||
/// })
|
||||
/// .and_then(|body| {
|
||||
/// format!("Body {:?}!", body);
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// })
|
||||
/// 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() {
|
||||
@ -94,11 +95,11 @@ impl Stream for Payload {
|
||||
impl FromRequest for Payload {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future = Result<Payload, Error>;
|
||||
type Future = Ready<Result<Payload, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
Ok(Payload(payload.take()))
|
||||
ok(Payload(payload.take()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,8 +131,10 @@ impl FromRequest for Payload {
|
||||
impl FromRequest for Bytes {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future =
|
||||
Either<Box<dyn Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
|
||||
type Future = Either<
|
||||
LocalBoxFuture<'static, Result<Bytes, Error>>,
|
||||
Ready<Result<Bytes, Error>>,
|
||||
>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
@ -144,13 +147,12 @@ impl FromRequest for Bytes {
|
||||
};
|
||||
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::B(err(e));
|
||||
return Either::Right(err(e));
|
||||
}
|
||||
|
||||
let limit = cfg.limit;
|
||||
Either::A(Box::new(
|
||||
HttpMessageBody::new(req, payload).limit(limit).from_err(),
|
||||
))
|
||||
let fut = HttpMessageBody::new(req, payload).limit(limit);
|
||||
Either::Left(async move { Ok(fut.await?) }.boxed_local())
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,8 +187,8 @@ impl FromRequest for String {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
Box<dyn Future<Item = String, Error = Error>>,
|
||||
FutureResult<String, Error>,
|
||||
LocalBoxFuture<'static, Result<String, Error>>,
|
||||
Ready<Result<String, Error>>,
|
||||
>;
|
||||
|
||||
#[inline]
|
||||
@ -201,33 +203,34 @@ impl FromRequest for String {
|
||||
|
||||
// check content-type
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::B(err(e));
|
||||
return Either::Right(err(e));
|
||||
}
|
||||
|
||||
// check charset
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(e) => return Either::B(err(e.into())),
|
||||
Err(e) => return Either::Right(err(e.into())),
|
||||
};
|
||||
let limit = cfg.limit;
|
||||
let fut = HttpMessageBody::new(req, payload).limit(limit);
|
||||
|
||||
Either::A(Box::new(
|
||||
HttpMessageBody::new(req, payload)
|
||||
.limit(limit)
|
||||
.from_err()
|
||||
.and_then(move |body| {
|
||||
if encoding == UTF_8 {
|
||||
Ok(str::from_utf8(body.as_ref())
|
||||
.map_err(|_| ErrorBadRequest("Can not decode body"))?
|
||||
.to_owned())
|
||||
} else {
|
||||
Ok(encoding
|
||||
.decode_without_bom_handling_and_without_replacement(&body)
|
||||
.map(|s| s.into_owned())
|
||||
.ok_or_else(|| ErrorBadRequest("Can not decode body"))?)
|
||||
}
|
||||
}),
|
||||
))
|
||||
Either::Left(
|
||||
async move {
|
||||
let body = fut.await?;
|
||||
|
||||
if encoding == UTF_8 {
|
||||
Ok(str::from_utf8(body.as_ref())
|
||||
.map_err(|_| ErrorBadRequest("Can not decode body"))?
|
||||
.to_owned())
|
||||
} else {
|
||||
Ok(encoding
|
||||
.decode_without_bom_handling_and_without_replacement(&body)
|
||||
.map(|s| s.into_owned())
|
||||
.ok_or_else(|| ErrorBadRequest("Can not decode body"))?)
|
||||
}
|
||||
}
|
||||
.boxed_local(),
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Payload configuration for request's payload.
|
||||
@ -300,7 +303,7 @@ pub struct HttpMessageBody {
|
||||
length: Option<usize>,
|
||||
stream: Option<dev::Decompress<dev::Payload>>,
|
||||
err: Option<PayloadError>,
|
||||
fut: Option<Box<dyn Future<Item = Bytes, Error = PayloadError>>>,
|
||||
fut: Option<LocalBoxFuture<'static, Result<Bytes, PayloadError>>>,
|
||||
}
|
||||
|
||||
impl HttpMessageBody {
|
||||
@ -346,42 +349,43 @@ impl HttpMessageBody {
|
||||
}
|
||||
|
||||
impl Future for HttpMessageBody {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
type Output = Result<Bytes, PayloadError>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
return Pin::new(fut).poll(cx);
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > self.limit {
|
||||
return Err(PayloadError::Overflow);
|
||||
return Poll::Ready(Err(PayloadError::Overflow));
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
self.fut = Some(Box::new(
|
||||
self.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(PayloadError::Overflow)
|
||||
let mut stream = self.stream.take().unwrap();
|
||||
self.fut = Some(
|
||||
async move {
|
||||
let mut body = BytesMut::with_capacity(8192);
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item?;
|
||||
if body.len() + chunk.len() > limit {
|
||||
return Err(PayloadError::Overflow);
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
})
|
||||
.map(|body| body.freeze()),
|
||||
));
|
||||
self.poll()
|
||||
}
|
||||
Ok(body.freeze())
|
||||
}
|
||||
.boxed_local(),
|
||||
);
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use std::{fmt, ops};
|
||||
|
||||
use actix_http::error::Error;
|
||||
use futures::future::{err, ok, Ready};
|
||||
use serde::de;
|
||||
use serde_urlencoded;
|
||||
|
||||
@ -132,7 +133,7 @@ where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
type Config = QueryConfig;
|
||||
|
||||
#[inline]
|
||||
@ -143,7 +144,7 @@ where
|
||||
.unwrap_or(None);
|
||||
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map(|val| Ok(Query(val)))
|
||||
.map(|val| ok(Query(val)))
|
||||
.unwrap_or_else(move |e| {
|
||||
let e = QueryPayloadError::Deserialize(e);
|
||||
|
||||
@ -159,7 +160,7 @@ where
|
||||
e.into()
|
||||
};
|
||||
|
||||
Err(e)
|
||||
err(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -227,7 +228,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::error::InternalError;
|
||||
use crate::test::TestRequest;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
|
||||
#[derive(Deserialize, Debug, Display)]
|
||||
@ -253,42 +254,46 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert!(Query::<Id>::from_request(&req, &mut pl).is_err());
|
||||
block_on(async {
|
||||
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());
|
||||
|
||||
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
|
||||
let mut s = Query::<Id>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }");
|
||||
let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }");
|
||||
|
||||
s.id = "test1".to_string();
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.id, "test1");
|
||||
s.id = "test1".to_string();
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.id, "test1");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_error_responder() {
|
||||
let req = TestRequest::with_uri("/name/user1/")
|
||||
.data(QueryConfig::default().error_handler(|e, _| {
|
||||
let resp = HttpResponse::UnprocessableEntity().finish();
|
||||
InternalError::from_response(e, resp).into()
|
||||
}))
|
||||
.to_srv_request();
|
||||
block_on(async {
|
||||
let req = TestRequest::with_uri("/name/user1/")
|
||||
.data(QueryConfig::default().error_handler(|e, _| {
|
||||
let resp = HttpResponse::UnprocessableEntity().finish();
|
||||
InternalError::from_response(e, resp).into()
|
||||
}))
|
||||
.to_srv_request();
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let query = Query::<Id>::from_request(&req, &mut pl);
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let query = Query::<Id>::from_request(&req, &mut pl).await;
|
||||
|
||||
assert!(query.is_err());
|
||||
assert_eq!(
|
||||
query
|
||||
.unwrap_err()
|
||||
.as_response_error()
|
||||
.error_response()
|
||||
.status(),
|
||||
StatusCode::UNPROCESSABLE_ENTITY
|
||||
);
|
||||
assert!(query.is_err());
|
||||
assert_eq!(
|
||||
query
|
||||
.unwrap_err()
|
||||
.as_response_error()
|
||||
.error_response()
|
||||
.status(),
|
||||
StatusCode::UNPROCESSABLE_ENTITY
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::str;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use futures::Stream;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::error::{PayloadError, ReadlinesError};
|
||||
@ -22,7 +26,7 @@ pub struct Readlines<T: HttpMessage> {
|
||||
impl<T> Readlines<T>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
|
||||
T::Stream: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
|
||||
{
|
||||
/// Create a new stream to read request line by line.
|
||||
pub fn new(req: &mut T) -> Self {
|
||||
@ -62,20 +66,21 @@ where
|
||||
impl<T> Stream for Readlines<T>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
|
||||
T::Stream: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
|
||||
{
|
||||
type Item = String;
|
||||
type Error = ReadlinesError;
|
||||
type Item = Result<String, ReadlinesError>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
let this = self.get_mut();
|
||||
|
||||
if let Some(err) = this.err.take() {
|
||||
return Poll::Ready(Some(Err(err)));
|
||||
}
|
||||
|
||||
// check if there is a newline in the buffer
|
||||
if !self.checked_buff {
|
||||
if !this.checked_buff {
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in self.buff.iter().enumerate() {
|
||||
for (ind, b) in this.buff.iter().enumerate() {
|
||||
if *b == b'\n' {
|
||||
found = Some(ind);
|
||||
break;
|
||||
@ -83,28 +88,28 @@ where
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
if ind + 1 > this.limit {
|
||||
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
|
||||
}
|
||||
let line = if self.encoding == UTF_8 {
|
||||
str::from_utf8(&self.buff.split_to(ind + 1))
|
||||
let line = if this.encoding == UTF_8 {
|
||||
str::from_utf8(&this.buff.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
this.encoding
|
||||
.decode_without_bom_handling_and_without_replacement(
|
||||
&self.buff.split_to(ind + 1),
|
||||
&this.buff.split_to(ind + 1),
|
||||
)
|
||||
.map(Cow::into_owned)
|
||||
.ok_or(ReadlinesError::EncodingError)?
|
||||
};
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
return Poll::Ready(Some(Ok(line)));
|
||||
}
|
||||
self.checked_buff = true;
|
||||
this.checked_buff = true;
|
||||
}
|
||||
// poll req for more bytes
|
||||
match self.stream.poll() {
|
||||
Ok(Async::Ready(Some(mut bytes))) => {
|
||||
match Pin::new(&mut this.stream).poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(mut bytes))) => {
|
||||
// check if there is a newline in bytes
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in bytes.iter().enumerate() {
|
||||
@ -115,15 +120,15 @@ where
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
if ind + 1 > this.limit {
|
||||
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
|
||||
}
|
||||
let line = if self.encoding == UTF_8 {
|
||||
let line = if this.encoding == UTF_8 {
|
||||
str::from_utf8(&bytes.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
this.encoding
|
||||
.decode_without_bom_handling_and_without_replacement(
|
||||
&bytes.split_to(ind + 1),
|
||||
)
|
||||
@ -131,83 +136,72 @@ where
|
||||
.ok_or(ReadlinesError::EncodingError)?
|
||||
};
|
||||
// extend buffer with rest of the bytes;
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
self.checked_buff = false;
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
this.buff.extend_from_slice(&bytes);
|
||||
this.checked_buff = false;
|
||||
return Poll::Ready(Some(Ok(line)));
|
||||
}
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
Ok(Async::NotReady)
|
||||
this.buff.extend_from_slice(&bytes);
|
||||
Poll::Pending
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(None)) => {
|
||||
if self.buff.is_empty() {
|
||||
return Ok(Async::Ready(None));
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => {
|
||||
if this.buff.is_empty() {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
if self.buff.len() > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
if this.buff.len() > this.limit {
|
||||
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
|
||||
}
|
||||
let line = if self.encoding == UTF_8 {
|
||||
str::from_utf8(&self.buff)
|
||||
let line = if this.encoding == UTF_8 {
|
||||
str::from_utf8(&this.buff)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode_without_bom_handling_and_without_replacement(&self.buff)
|
||||
this.encoding
|
||||
.decode_without_bom_handling_and_without_replacement(&this.buff)
|
||||
.map(Cow::into_owned)
|
||||
.ok_or(ReadlinesError::EncodingError)?
|
||||
};
|
||||
self.buff.clear();
|
||||
Ok(Async::Ready(Some(line)))
|
||||
this.buff.clear();
|
||||
Poll::Ready(Some(Ok(line)))
|
||||
}
|
||||
Err(e) => Err(ReadlinesError::from(e)),
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
use super::*;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
|
||||
#[test]
|
||||
fn test_readlines() {
|
||||
let mut req = TestRequest::default()
|
||||
block_on(async {
|
||||
let mut req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(
|
||||
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
|
||||
industry. Lorem Ipsum has been the industry's standard dummy\n\
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text.",
|
||||
))
|
||||
.to_request();
|
||||
let stream = match block_on(Readlines::new(&mut req).into_future()) {
|
||||
Ok((Some(s), stream)) => {
|
||||
assert_eq!(
|
||||
s,
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
|
||||
);
|
||||
stream
|
||||
}
|
||||
_ => unreachable!("error"),
|
||||
};
|
||||
|
||||
let stream = match block_on(stream.into_future()) {
|
||||
Ok((Some(s), stream)) => {
|
||||
assert_eq!(
|
||||
s,
|
||||
"industry. Lorem Ipsum has been the industry's standard dummy\n"
|
||||
);
|
||||
stream
|
||||
}
|
||||
_ => unreachable!("error"),
|
||||
};
|
||||
let mut stream = Readlines::new(&mut req);
|
||||
assert_eq!(
|
||||
stream.next().await.unwrap().unwrap(),
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
|
||||
);
|
||||
|
||||
match block_on(stream.into_future()) {
|
||||
Ok((Some(s), _)) => {
|
||||
assert_eq!(
|
||||
s,
|
||||
"Contrary to popular belief, Lorem Ipsum is not simply random text."
|
||||
);
|
||||
}
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
assert_eq!(
|
||||
stream.next().await.unwrap().unwrap(),
|
||||
"industry. Lorem Ipsum has been the industry's standard dummy\n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
stream.next().await.unwrap().unwrap(),
|
||||
"Contrary to popular belief, Lorem Ipsum is not simply random text."
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
35
src/web.rs
35
src/web.rs
@ -1,11 +1,12 @@
|
||||
//! Essentials helper functions and types for application registration.
|
||||
use actix_http::http::Method;
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::Future;
|
||||
|
||||
pub use actix_http::Response as HttpResponse;
|
||||
pub use bytes::{Bytes, BytesMut};
|
||||
pub use futures::channel::oneshot::Canceled;
|
||||
|
||||
use crate::error::{BlockingError, Error};
|
||||
use crate::error::Error;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::handler::{AsyncFactory, Factory};
|
||||
use crate::resource::Resource;
|
||||
@ -256,21 +257,21 @@ where
|
||||
/// # use futures::future::{ok, Future};
|
||||
/// use actix_web::{web, App, HttpResponse, Error};
|
||||
///
|
||||
/// fn index() -> impl Future<Item=HttpResponse, Error=Error> {
|
||||
/// ok(HttpResponse::Ok().finish())
|
||||
/// async fn index() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// }
|
||||
///
|
||||
/// App::new().service(web::resource("/").route(
|
||||
/// web::to_async(index))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn to_async<F, I, R>(handler: F) -> Route
|
||||
pub fn to_async<F, I, R, O, E>(handler: F) -> Route
|
||||
where
|
||||
F: AsyncFactory<I, R>,
|
||||
F: AsyncFactory<I, R, O, E>,
|
||||
I: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
R: Future<Output = Result<O, E>> + 'static,
|
||||
O: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
Route::new().to_async(handler)
|
||||
}
|
||||
@ -279,10 +280,11 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{dev, web, guard, App, HttpResponse};
|
||||
/// use futures::future::{ok, Ready};
|
||||
/// use actix_web::{dev, web, guard, App, Error, HttpResponse};
|
||||
///
|
||||
/// fn my_service(req: dev::ServiceRequest) -> dev::ServiceResponse {
|
||||
/// req.into_response(HttpResponse::Ok().finish())
|
||||
/// fn my_service(req: dev::ServiceRequest) -> Ready<Result<dev::ServiceResponse, Error>> {
|
||||
/// ok(req.into_response(HttpResponse::Ok().finish()))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -299,11 +301,10 @@ pub fn service(path: &str) -> WebService {
|
||||
|
||||
/// Execute blocking function on a thread pool, returns future that resolves
|
||||
/// to result of the function execution.
|
||||
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
|
||||
pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, Canceled>>
|
||||
where
|
||||
F: FnOnce() -> Result<I, E> + Send + 'static,
|
||||
I: Send + 'static,
|
||||
E: Send + std::fmt::Debug + 'static,
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
actix_threadpool::run(f).from_err()
|
||||
actix_threadpool::run(f)
|
||||
}
|
||||
|
@ -59,19 +59,5 @@ tokio-timer = "0.3.0-alpha.6"
|
||||
open-ssl = { version="0.10", package="openssl", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
#actix-web = "1.0.7"
|
||||
actix-web = "2.0.0-alpha.1"
|
||||
actix-http = "0.3.0-alpha.1"
|
||||
|
||||
[patch.crates-io]
|
||||
actix-http = { path = "../actix-http" }
|
||||
awc = { path = "../awc" }
|
||||
|
||||
actix-codec = { path = "../../actix-net/actix-codec" }
|
||||
actix-connect = { path = "../../actix-net/actix-connect" }
|
||||
actix-rt = { path = "../../actix-net/actix-rt" }
|
||||
actix-server = { path = "../../actix-net/actix-server" }
|
||||
actix-server-config = { path = "../../actix-net/actix-server-config" }
|
||||
actix-service = { path = "../../actix-net/actix-service" }
|
||||
actix-testing = { path = "../../actix-net/actix-testing" }
|
||||
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
|
||||
actix-utils = { path = "../../actix-net/actix-utils" }
|
||||
|
@ -3,7 +3,7 @@ use std::sync::mpsc;
|
||||
use std::{net, thread, time};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::{System};
|
||||
use actix_rt::System;
|
||||
use actix_server::{Server, ServiceFactory};
|
||||
use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector};
|
||||
use bytes::Bytes;
|
||||
|
@ -2,8 +2,8 @@ use net2::TcpBuilder;
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread, time::Duration};
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
use openssl::ssl::SslAcceptorBuilder;
|
||||
#[cfg(feature = "openssl")]
|
||||
use open_ssl::ssl::SslAcceptorBuilder;
|
||||
|
||||
use actix_http::Response;
|
||||
use actix_web::{test, web, App, HttpServer};
|
||||
@ -55,22 +55,19 @@ fn test_start() {
|
||||
use actix_http::client;
|
||||
use actix_web::test;
|
||||
|
||||
let client = test::run_on(|| {
|
||||
Ok::<_, ()>(
|
||||
awc::Client::build()
|
||||
.connector(
|
||||
client::Connector::new()
|
||||
.timeout(Duration::from_millis(100))
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let host = format!("http://{}", addr);
|
||||
test::block_on(async {
|
||||
let client = awc::Client::build()
|
||||
.connector(
|
||||
client::Connector::new()
|
||||
.timeout(Duration::from_millis(100))
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let response = test::block_on(client.get(host.clone()).send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
let host = format!("http://{}", addr);
|
||||
let response = client.get(host.clone()).send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
});
|
||||
}
|
||||
|
||||
// stop
|
||||
@ -80,9 +77,9 @@ fn test_start() {
|
||||
let _ = sys.stop();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
fn ssl_acceptor() -> std::io::Result<SslAcceptorBuilder> {
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder
|
||||
@ -95,7 +92,7 @@ fn ssl_acceptor() -> std::io::Result<SslAcceptorBuilder> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
fn test_start_ssl() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
@ -113,7 +110,7 @@ fn test_start_ssl() {
|
||||
.shutdown_timeout(1)
|
||||
.system_exit()
|
||||
.disable_signals()
|
||||
.bind_ssl(format!("{}", addr), builder)
|
||||
.bind_openssl(format!("{}", addr), builder)
|
||||
.unwrap()
|
||||
.start();
|
||||
|
||||
@ -122,30 +119,27 @@ fn test_start_ssl() {
|
||||
});
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let client = test::run_on(|| {
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
test::block_on(async move {
|
||||
use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
|
||||
Ok::<_, ()>(
|
||||
awc::Client::build()
|
||||
.connector(
|
||||
awc::Connector::new()
|
||||
.ssl(builder.build())
|
||||
.timeout(Duration::from_millis(100))
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let host = format!("https://{}", addr);
|
||||
let client = awc::Client::build()
|
||||
.connector(
|
||||
awc::Connector::new()
|
||||
.ssl(builder.build())
|
||||
.timeout(Duration::from_millis(100))
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let response = test::block_on(client.get(host.clone()).send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
let host = format!("https://{}", addr);
|
||||
let response = client.get(host.clone()).send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
});
|
||||
|
||||
// stop
|
||||
let _ = srv.stop(false);
|
||||
|
1197
tests/test_server.rs
1197
tests/test_server.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user