diff --git a/Cargo.toml b/Cargo.toml index 0b5c4f3d1..b1aa79527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.9" +version = "2.0.0-alpha.1" authors = ["Nikolay Kim "] 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" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 091c94044..57aa5833a 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-cors" -version = "0.1.0" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] 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" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 1bc063e55..6e33bb412 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.7" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] 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"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 16f40a20c..8df7a6aa9 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -303,9 +303,8 @@ impl Files { /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where - for<'r, 's> F: - Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, + for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result + + 'static, { self.renderer = Rc::new(f); self diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 321041c7e..9d32ebed5 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.2.1" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] 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" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index edc93f09b..32af97ad9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -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" } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 8fa35feab..7e1dae58f 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -172,12 +172,11 @@ where /// Finish service configuration and create *http service* for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where - B: MessageBody + 'static, + B: MessageBody, F: IntoServiceFactory, - S::Error: Into + 'static, + S::Error: Into, S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, + S::Response: Into>, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 96775b989..1a52a60f2 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -51,10 +51,10 @@ impl Dispatcher where T: IoStream, S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, + S::Error: Into, + // S::Future: 'static, + S::Response: Into>, + B: MessageBody, { pub(crate) fn new( service: CloneableService, @@ -176,9 +176,9 @@ enum ServiceResponseState { impl ServiceResponse where F: Future>, - E: Into + 'static, - I: Into> + 'static, - B: MessageBody + 'static, + E: Into, + I: Into>, + B: MessageBody, { fn prepare_response( &self, @@ -244,9 +244,9 @@ where impl Future for ServiceResponse where F: Future>, - E: Into + 'static, - I: Into> + 'static, - B: MessageBody + 'static, + E: Into, + I: Into>, + B: MessageBody, { type Output = (); diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index e645275a2..d05b37685 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-identity" -version = "0.1.0" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] 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" \ No newline at end of file diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 2168c259a..804d1bb67 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.4" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] 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" \ No newline at end of file +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" \ No newline at end of file diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index d973661ef..3ce2a8b40 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.2.0" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] 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" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f8f9a7eb8..70d89d5de 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -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" } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index cb38c7315..8d7bc2274 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -13,8 +13,8 @@ use futures::Future; use rand::Rng; use actix_http::HttpService; -use actix_http_test::TestServer; -use actix_service::{service_fn, NewService}; +use actix_http_test::{bloxk_on, TestServer}; +use actix_service::{service_fn, ServiceFactory}; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -44,459 +44,497 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_simple() { - let mut srv = - TestServer::new(|| { + block_on(async { + let mut srv = TestServer::start(|| { HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), )) }); - let request = srv.get("/").header("x-test", "111").send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // camel case - let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); - assert!(response.status().is_success()); + // camel case + let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_json() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service( - web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), - )) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|_: web::Json| HttpResponse::Ok())), + ), + ) + }); - let request = srv - .get("/") - .header("x-test", "111") - .send_json(&"TEST".to_string()); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .get("/") + .header("x-test", "111") + .send_json(&"TEST".to_string()); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_form() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |_: web::Form>| HttpResponse::Ok(), - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |_: web::Form>| HttpResponse::Ok(), + )))) + }); - let mut data = HashMap::new(); - let _ = data.insert("key".to_string(), "TEST".to_string()); + let mut data = HashMap::new(); + let _ = data.insert("key".to_string(), "TEST".to_string()); - let request = srv.get("/").header("x-test", "111").send_form(&data); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send_form(&data); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_timeout() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to_async( - || { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route( + web::to_async(|| { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }), + ))) + }); - let client = srv.execute(|| { - awc::Client::build() - .timeout(Duration::from_millis(50)) - .finish() - }); - let request = client.get(srv.url("/")).send(); - match srv.block_on(request) { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } + let client = srv.execute(|| { + awc::Client::build() + .timeout(Duration::from_millis(50)) + .finish() + }); + let request = client.get(srv.url("/")).send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } + }) } #[test] fn test_timeout_override() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to_async( - || { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route( + web::to_async(|| { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }), + ))) + }); - let client = awc::Client::build() - .timeout(Duration::from_millis(50000)) - .finish(); - let request = client - .get(srv.url("/")) - .timeout(Duration::from_millis(50)) - .send(); - match srv.block_on(request) { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } + let client = awc::Client::build() + .timeout(Duration::from_millis(50000)) + .finish(); + let request = client + .get(srv.url("/")) + .timeout(Duration::from_millis(50)) + .send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } + }) } #[test] fn test_connection_reuse() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - )) - }); + let mut srv = TestServer::start(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + ))) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } #[test] fn test_connection_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + ))) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).force_close().send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).force_close().send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")).force_close(); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")).force_close(); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_connection_server_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().finish())), - ), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().finish())), + ), + )) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_connection_wait_queue() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ))) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ))) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); - // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = srv.block_on(req2_fut).unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } #[test] fn test_connection_wait_queue_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), - ), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), + ), + )) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); - // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = srv.block_on(req2_fut).unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_with_query_parameter() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").to( - |req: HttpRequest| { - if req.query_string().contains("qp") { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }, - ))) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest| { + if req.query_string().contains("qp") { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }, + ))) + }); - let res = srv - .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) - .unwrap(); - assert!(res.status().is_success()); + let res = srv + .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) + .unwrap(); + assert!(res.status().is_success()); + }) } #[test] fn test_no_decompress() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(|| { - let mut res = HttpResponse::Ok().body(STR); - res.encoding(header::ContentEncoding::Gzip); - res - })), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(|| { + let mut res = HttpResponse::Ok().body(STR); + res.encoding(header::ContentEncoding::Gzip); + res + })), + )) + }); - let mut res = srv - .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) - .unwrap(); - assert!(res.status().is_success()); + let mut res = srv + .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); - // read response - let bytes = srv.block_on(res.body()).unwrap(); + // read response + let bytes = srv.block_on(res.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - // POST - let mut res = srv - .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) - .unwrap(); - assert!(res.status().is_success()); + // POST + let mut res = srv + .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); - let bytes = srv.block_on(res.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let bytes = srv.block_on(res.body()).unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_client_gzip_encoding() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let data = e.finish().unwrap(); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - })))) - }); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_client_gzip_encoding_large() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.repeat(10).as_ref()).unwrap(); - let data = e.finish().unwrap(); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.repeat(10).as_ref()).unwrap(); + let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - })))) - }); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(STR.repeat(10))); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(STR.repeat(10))); + }) } #[test] fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(100_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(100_000) + .collect::(); - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }, - )))) - }); + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] fn test_client_brotli_encoding() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "br") - .body(data) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "br") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } // #[test] @@ -644,65 +682,67 @@ fn test_client_brotli_encoding() { #[test] fn test_client_cookie_handling() { - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); + block_on(async { + fn err() -> Error { + use std::io::{Error as IoError, ErrorKind}; + // stub some generic error + Error::from(IoError::from(ErrorKind::NotFound)) + } + let cookie1 = Cookie::build("cookie1", "value1").finish(); + let cookie2 = Cookie::build("cookie2", "value2") + .domain("www.example.org") + .path("/") + .secure(true) + .http_only(true) + .finish(); + // Q: are all these clones really necessary? A: Yes, possibly + let cookie1b = cookie1.clone(); + let cookie2b = cookie2.clone(); - let mut srv = TestServer::new(move || { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); + let mut srv = TestServer::new(move || { + let cookie1 = cookie1b.clone(); + let cookie2 = cookie2b.clone(); - HttpService::new(App::new().route( - "/", - web::to(move |req: HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }) - .and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }), - )) - }); + HttpService::new(App::new().route( + "/", + web::to(move |req: HttpRequest| { + // Check cookies were sent correctly + req.cookie("cookie1") + .ok_or_else(err) + .and_then(|c1| { + if c1.value() == "value1" { + Ok(()) + } else { + Err(err()) + } + }) + .and_then(|()| req.cookie("cookie2").ok_or_else(err)) + .and_then(|c2| { + if c2.value() == "value2" { + Ok(()) + } else { + Err(err()) + } + }) + // Send some cookies back + .map(|_| { + HttpResponse::Ok() + .cookie(cookie1.clone()) + .cookie(cookie2.clone()) + .finish() + }) + }), + )) + }); - let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); + let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + let c1 = response.cookie("cookie1").expect("Missing cookie1"); + assert_eq!(c1, cookie1); + let c2 = response.cookie("cookie2").expect("Missing cookie2"); + assert_eq!(c2, cookie2); + }) } // #[test] @@ -737,56 +777,60 @@ fn test_client_cookie_handling() { #[test] fn client_basic_auth() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + // set authorization header to Basic + let request = srv.get("/").basic_auth("username", Some("password")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn client_bearer_auth() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Bearer someS3cr3tAutht0k3n" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Bearer someS3cr3tAutht0k3n" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Bearer - let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + // set authorization header to Bearer + let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + }) } diff --git a/examples/basic.rs b/examples/basic.rs index 46440d706..76c977322 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -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 { format!("Hello: {}!\r\n", name) } -fn index_async(req: HttpRequest) -> impl IntoFuture { +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( diff --git a/examples/client.rs b/examples/client.rs index 8a75fd306..90a362fe3 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -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(()) + }) } diff --git a/examples/uds.rs b/examples/uds.rs index 9dc82903f..7da41a2c5 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -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 { format!("Hello: {}!\r\n", name) } -fn index_async(req: HttpRequest) -> impl IntoFuture { +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)), ) diff --git a/src/app.rs b/src/app.rs index f93859c7e..288256604 100644 --- a/src/app.rs +++ b/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 Box, Error = ()>>>; + Box LocalBoxFuture<'static, Result, ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App { endpoint: T, - services: Vec>, + services: Vec>, default: Option>, factory_ref: Rc>>, data: Vec>, @@ -61,7 +64,7 @@ impl App { impl App where B: MessageBody, - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -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(mut self, data: F) -> Self + pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, - Out: IntoFuture + 'static, - Out::Error: std::fmt::Debug, + Out: Future> + '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 = 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 = Box::new(Data::new(data)); + Ok(data) + } + } + } + } + .boxed_local() })); self } @@ -267,8 +276,8 @@ where /// ``` pub fn default_service(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + 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( + pub fn wrap( self, - mw: F, + mw: M, ) -> App< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -372,11 +379,9 @@ where InitError = (), >, B1: MessageBody, - F: IntoTransform, { - 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, @@ -433,16 +441,26 @@ where where B1: MessageBody, F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: IntoFuture, Error = Error>, + R: Future, 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 IntoNewService> for App +impl IntoServiceFactory> for App where B: MessageBody, - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -450,7 +468,7 @@ where InitError = (), >, { - fn into_new_service(self) -> AppInit { + fn into_factory(self) -> AppInit { 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| 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| 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| 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| 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( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, 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")); + }) } } diff --git a/src/app_service.rs b/src/app_service.rs index 513b4aa4b..7407ee2fb 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -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>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = Either< - FutureResult, - Box>, ->; +type BoxedResponse = LocalBoxFuture<'static, Result>; type FnDataFactory = - Box Box, Error = ()>>>; + Box LocalBoxFuture<'static, Result, ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. pub struct AppInit where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -44,15 +43,15 @@ where pub(crate) data: Rc>>, pub(crate) data_factories: Rc>, pub(crate) config: RefCell, - pub(crate) services: Rc>>>, + pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, } -impl NewService for AppInit +impl ServiceFactory for AppInit where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -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 where - T: NewService, + T: ServiceFactory, { endpoint: Option, + #[pin] endpoint_fut: T::Future, rmap: Rc, config: AppConfig, data: Rc>>, data_factories: Vec>, - data_factories_fut: Vec, Error = ()>>>, + data_factories_fut: Vec, ()>>>, _t: PhantomData, } impl Future for AppInitResult where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -159,48 +160,49 @@ where InitError = (), >, { - type Item = AppInitService; - type Error = (); + type Output = Result, ()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); - fn poll(&mut self) -> Poll { // 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> { + self.service.poll_ready(cx) } fn call(&mut self, req: Request) -> Self::Future { @@ -270,7 +272,7 @@ pub struct AppRoutingFactory { default: Rc, } -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>; +type HttpServiceFut = LocalBoxFuture<'static, Result>; /// Create app service #[doc(hidden)] pub struct AppRoutingFactoryResponse { fut: Vec, default: Option, - default_fut: Option>>, + default_fut: Option>>, } enum CreateAppRoutingItem { @@ -314,16 +316,15 @@ enum CreateAppRoutingItem { } impl Future for AppRoutingFactoryResponse { - type Item = AppRouting; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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> { 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)); } } diff --git a/src/config.rs b/src/config.rs index 63fd31d27..3ce18f98b 100644 --- a/src/config.rs +++ b/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>>, - service: F, + factory: F, nested: Option>, ) where - F: IntoNewService, - S: NewService< + F: IntoServiceFactory, + 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>, + pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, } @@ -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| 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); + }) } } diff --git a/src/data.rs b/src/data.rs index 14e293bc2..a11175c12 100644 --- a/src/data.rs +++ b/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 Clone for Data { impl FromRequest for Data { type Config = (); type Error = Error; - type Future = Result; + type Future = Ready>; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.get_app_data::() { - 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| 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| 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| HttpResponse::Ok()), - )); + block_on(async { + let mut srv = + init_service(App::new().register_data(Data::new(10usize)).service( + web::resource("/").to(|_: web::Data| 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| 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| 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| { - 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| { + 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| 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| 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| { - 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| { + 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); + }) } } diff --git a/src/extract.rs b/src/extract.rs index 425637311..20a1180ec 100644 --- a/src/extract.rs +++ b/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; /// Future that resolves to a Self - type Future: IntoFuture; + type Future: Future>; /// 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; +/// type Future = Ready>; /// 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, Error = Error>>; + type Future = LocalBoxFuture<'static, Result, 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 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 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; +/// type Future = Ready>; /// 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 FromRequest for Result +impl FromRequest for Result 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, Error = Error>>; + type Future = LocalBoxFuture<'static, Result, 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>; 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 { + let this = self.project(); - fn poll(&mut self) -> Poll { 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 } } } diff --git a/src/handler.rs b/src/handler.rs index 078abbf1d..7f5d52945 100644 --- a/src/handler.rs +++ b/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<::Future>; + type Future = HandlerServiceResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + 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 { - fut: T, +#[pin_project] +pub struct HandlerServiceResponse { + #[pin] + fut: T::Future, req: Option, } -impl Future for HandlerServiceResponse -where - T: Future, - T::Error: Into, -{ - type Item = ServiceResponse; - type Error = Infallible; +impl Future for HandlerServiceResponse { + type Output = Result; - fn poll(&mut self) -> Poll { - 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 { + 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: Clone + 'static +pub trait AsyncFactory: Clone + 'static where - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + R: Future>, + O: Responder, + E: Into, { fn call(&self, param: T) -> R; } -impl AsyncFactory<(), R> for F +impl AsyncFactory<(), R, O, E> for F where F: Fn() -> R + Clone + 'static, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + R: Future>, + O: Responder, + E: Into, { fn call(&self, _: ()) -> R { (self)() @@ -142,23 +141,23 @@ where } #[doc(hidden)] -pub struct AsyncHandler +pub struct AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { hnd: F, - _t: PhantomData<(T, R)>, + _t: PhantomData<(T, R, O, E)>, } -impl AsyncHandler +impl AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { pub fn new(hnd: F) -> Self { AsyncHandler { @@ -168,12 +167,12 @@ where } } -impl Clone for AsyncHandler +impl Clone for AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { fn clone(&self) -> Self { AsyncHandler { @@ -183,25 +182,25 @@ where } } -impl Service for AsyncHandler +impl Service for AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Infallible; - type Future = AsyncHandlerServiceResponse; + type Future = AsyncHandlerServiceResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + 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 +#[pin_project] +pub struct AsyncHandlerServiceResponse where - T: Future, - T::Item: Responder, + T: Future>, + R: Responder, + E: Into, { + #[pin] fut: T, - fut2: Option<<::Future as IntoFuture>::Future>, + #[pin] + fut2: Option, req: Option, } -impl Future for AsyncHandlerServiceResponse +impl Future for AsyncHandlerServiceResponse where - T: Future, - T::Item: Responder, - T::Error: Into, + T: Future>, + R: Responder, + E: Into, { - type Item = ServiceResponse; - type Error = Infallible; + type Output = Result; - fn poll(&mut self) -> Poll { - 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 { + 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 Extract { } } -impl NewService for Extract +impl ServiceFactory for Extract where S: Service< Request = (T, HttpRequest), @@ -293,7 +290,7 @@ where type Error = (Error, ServiceRequest); type InitError = (); type Service = ExtractService; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &()) -> Self::Future { ok(ExtractService { @@ -321,13 +318,13 @@ where type Error = (Error, ServiceRequest); type Future = ExtractResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + 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 { req: HttpRequest, service: S, - fut: ::Future, + #[pin] + fut: T::Future, + #[pin] fut_s: Option, } @@ -353,21 +353,26 @@ where Error = Infallible, >, { - type Item = ServiceResponse; - type Error = (Error, ServiceRequest); + type Output = Result; - fn poll(&mut self) -> Poll { - 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 { + 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 AsyncFactory<($($T,)+), Res> for Func + impl AsyncFactory<($($T,)+), Res, O, E1> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: IntoFuture, - Res::Item: Responder, - Res::Error: Into, + Res: Future>, + O: Responder, + E1: Into, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) diff --git a/src/lib.rs b/src/lib.rs index 60c34489e..1ae81505a 100644 --- a/src/lib.rs +++ b/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::{ diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 86665d824..a697deaec 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -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; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(CompressMiddleware { @@ -103,8 +106,8 @@ where type Error = Error; type Future = CompressResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + 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 where S: Service, B: MessageBody, { + #[pin] fut: S::Future, encoding: ContentEncoding, _t: PhantomData<(B)>, @@ -143,21 +148,25 @@ where B: MessageBody, S: Service, Error = Error>, { - type Item = ServiceResponse>; - type Error = Error; + type Output = Result>, Error>; - fn poll(&mut self) -> Poll { - let resp = futures::try_ready!(self.fut.poll()); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); - let enc = if let Some(enc) = resp.response().extensions().get::() { - 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.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)), + } } } diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ab2d36c2c..5c995503a 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -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; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(DefaultHeadersMiddleware { @@ -119,16 +121,19 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + 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" + ); + }) } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f450f0481..45df4bf34 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -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; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(LoggerMiddleware { @@ -151,8 +153,8 @@ where type Error = Error; type Future = LoggerResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + 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 where B: MessageBody, S: Service, { + #[pin] fut: S::Future, time: time::Tm, format: Option, @@ -197,11 +201,15 @@ where B: MessageBody, S: Service, Error = Error>, { - type Item = ServiceResponse>; - type Error = Error; + type Output = Result>, Error>; - fn poll(&mut self) -> Poll { - let res = futures::try_ready!(self.fut.poll()); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + 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 MessageBody for StreamLog { self.body.size() } - fn poll_next(&mut self) -> Poll, Error> { - match self.body.poll_next()? { - Async::Ready(Some(chunk)) => { + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { + 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"); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 84e0758bf..30acad15a 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -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; diff --git a/src/request.rs b/src/request.rs index ea27e303c..84744af28 100644 --- a/src/request.rs +++ b/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; + type Future = Ready>; #[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::().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::().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::().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::().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>, - } - 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>, + } + 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); + }) } } diff --git a/src/resource.rs b/src/resource.rs index 3ee0167a0..553d41568 100644 --- a/src/resource.rs +++ b/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 Resource 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 { - /// ok(HttpResponse::Ok().finish()) + /// async fn index(req: HttpRequest) -> Result { + /// 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> { + /// # async fn index(req: HttpRequest) -> Result { /// # unimplemented!() /// # } /// App::new().service(web::resource("/").route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + '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( + pub fn wrap( 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, { - 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, + R: Future>, { - 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(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + 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 HttpServiceFactory for Resource where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -423,9 +436,9 @@ where } } -impl IntoNewService for Resource +impl IntoServiceFactory for Resource 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>>>, } -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, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } impl Future for CreateResourceService { - type Item = ResourceService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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, - Box>, + Ready>, + LocalBoxFuture<'static, Result>, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + 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( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, 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, - data2: web::Data, - data3: web::Data| { - 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, + data2: web::Data, + data3: web::Data| { + 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); + }) } } diff --git a/src/responder.rs b/src/responder.rs index 4988ad5bc..2bb422b2e 100644 --- a/src/responder.rs +++ b/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; /// The future response value. - type Future: IntoFuture; + type Future: Future>; /// 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; + type Future = Ready>; #[inline] fn respond_to(self, _: &HttpRequest) -> Self::Future { @@ -84,15 +90,14 @@ where T: Responder, { type Error = T::Error; - type Future = EitherFuture< - ::Future, - FutureResult, - >; + type Future = EitherFuture>>; 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<::Future>, - FutureResult, + ResponseFuture, + Ready>, >; 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; + type Future = Ready>; #[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; + type Future = Ready>; 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; + type Future = Ready>; 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; + type Future = Ready>; 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; + type Future = Ready>; 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; + type Future = Ready>; 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; + type Future = Ready>; 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; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -299,34 +302,40 @@ impl Responder for CustomResponder { 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 { - fut: ::Future, + #[pin] + fut: T::Future, status: Option, headers: Option, } impl Future for CustomResponderFut { - type Item = Response; - type Error = T::Error; + type Output = Result; - fn poll(&mut self) -> Poll { - let mut res = try_ready!(self.fut.poll()); - if let Some(status) = self.status { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + 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 Future for CustomResponderFut { /// # use futures::future::{ok, Future}; /// use actix_web::{Either, Error, HttpResponse}; /// -/// type RegisterResult = -/// Either>>; +/// type RegisterResult = Either>; /// /// fn index() -> RegisterResult { /// if is_a_variant() { @@ -346,9 +354,9 @@ impl Future for CustomResponderFut { /// } 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< - ::Future, - ::Future, - >; + type Future = EitherResponder; 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 where - A: Future, - A::Error: Into, - B: Future, - B::Error: Into, + A: Responder, + B: Responder, { - A(A), - B(B), + A(#[pin] A::Future), + B(#[pin] B::Future), } impl Future for EitherResponder where - A: Future, - A::Error: Into, - B: Future, - B::Error: Into, + A: Responder, + B: Responder, { - type Item = Response; - type Error = Error; + type Output = Result; - fn poll(&mut self) -> Poll { - 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 { + #[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 Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Error = Error; - type Future = Box>; - - #[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 Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, { type Error = Error; - type Future = Result; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { let err: Error = self.into(); - Ok(err.into()) + ok(err.into()) } } -pub struct ResponseFuture(T); +#[pin_project] +pub struct ResponseFuture { + #[pin] + fut: T, + _t: PhantomData, +} -impl ResponseFuture { +impl ResponseFuture { pub fn new(fut: T) -> Self { - ResponseFuture(fut) + ResponseFuture { + fut, + _t: PhantomData, + } } } -impl Future for ResponseFuture +impl Future for ResponseFuture where - T: Future, - T::Error: Into, + T: Future>, + E: Into, { - type Item = Response; - type Error = Error; + type Output = Result; - fn poll(&mut self) -> Poll { - Ok(self.0.poll().map_err(|e| e.into())?) + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + 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 - 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 + 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::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req), - ); - assert!(res.is_err()); + let res = Err::(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") + ); + }) } } diff --git a/src/route.rs b/src/route.rs index 35b842944..fb46dbfd2 100644 --- a/src/route.rs +++ b/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 = Box< Request = Req, Response = Res, Error = Error, - Future = Either< - FutureResult, - Box>, - >, + Future = LocalBoxFuture<'static, Result>, >, >; type BoxedRouteNewService = Box< - dyn NewService< + dyn ServiceFactory< Config = (), Request = Req, Response = Res, Error = Error, InitError = (), Service = BoxedRouteService, - Future = Box, Error = ()>>, + Future = LocalBoxFuture<'static, Result, ()>>, >, >; @@ -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, Error = ()>, +type RouteFuture = LocalBoxFuture< + 'static, + Result, ()>, >; +#[pin_project::pin_project] pub struct CreateRouteService { + #[pin] fut: RouteFuture, guards: Rc>>, } impl Future for CreateRouteService { - type Item = RouteService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::Ready(service) => Ok(Async::Ready(RouteService { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + 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, - Box>, - >; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + 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) -> impl Future { - /// ok("Hello World!") + /// async fn index(info: web::Path) -> Result<&'static str, Error> { + /// Ok("Hello World!") /// } /// /// fn main() { @@ -261,13 +261,13 @@ impl Route { /// } /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, T: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + 'static, { self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( handler, @@ -278,14 +278,14 @@ impl Route { struct RouteNewService where - T: NewService, + T: ServiceFactory, { service: T, } impl RouteNewService where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -300,9 +300,9 @@ where } } -impl NewService for RouteNewService +impl ServiceFactory for RouteNewService where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -318,19 +318,20 @@ where type Error = Error; type InitError = (); type Service = BoxedRouteService; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; 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, - Box>, - >; + type Future = LocalBoxFuture<'static, Result>; - 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> { + 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::(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::(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::(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\"}")); + }) } } diff --git a/src/scope.rs b/src/scope.rs index c152bc334..2e59352d6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,15 +1,16 @@ use std::cell::RefCell; use std::fmt; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; 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, Future, FutureResult}; -use futures::{Async, IntoFuture, Poll}; +use futures::future::{ok, Either, Future, LocalBoxFuture, Ready}; use crate::config::ServiceConfig; use crate::data::Data; @@ -20,16 +21,13 @@ use crate::resource::Resource; use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ - ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, + AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = Either< - FutureResult, - Box>, ->; +type BoxedResponse = LocalBoxFuture<'static, Result>; /// Resources scope. /// @@ -64,7 +62,7 @@ pub struct Scope { endpoint: T, rdef: String, data: Option, - services: Vec>, + services: Vec>, guards: Vec>, default: Rc>>>, external: Vec, @@ -90,7 +88,7 @@ impl Scope { impl Scope where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -285,8 +283,8 @@ where /// If default resource is not registered, app's default resource is being used. pub fn default_service(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -295,8 +293,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) }), ))))); @@ -313,11 +311,11 @@ where /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( + pub fn wrap( self, - mw: F, + mw: M, ) -> Scope< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -333,11 +331,9 @@ where Error = Error, InitError = (), >, - F: IntoTransform, { - let endpoint = apply_transform(mw, self.endpoint); Scope { - endpoint, + endpoint: apply(mw, self.endpoint), rdef: self.rdef, data: self.data, guards: self.guards, @@ -368,13 +364,16 @@ where /// fn main() { /// let app = App::new().service( /// web::scope("/app") - /// .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))); /// } /// ``` @@ -382,7 +381,7 @@ where self, mw: F, ) -> Scope< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -392,15 +391,24 @@ where > where F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: IntoFuture, + R: Future>, { - self.wrap(mw) + Scope { + endpoint: apply_fn_factory(self.endpoint, mw), + rdef: self.rdef, + data: self.data, + guards: self.guards, + services: self.services, + default: self.default, + external: self.external, + factory_ref: self.factory_ref, + } } } impl HttpServiceFactory for Scope where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -471,7 +479,7 @@ pub struct ScopeFactory { default: Rc>>>, } -impl NewService for ScopeFactory { +impl ServiceFactory for ScopeFactory { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -508,14 +516,15 @@ impl NewService for ScopeFactory { /// Create scope service #[doc(hidden)] +#[pin_project::pin_project] pub struct ScopeFactoryResponse { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } -type HttpServiceFut = Box>; +type HttpServiceFut = LocalBoxFuture<'static, Result>; enum CreateScopeServiceItem { Future(Option, Option, HttpServiceFut), @@ -523,16 +532,15 @@ enum CreateScopeServiceItem { } impl Future for ScopeFactoryResponse { - type Item = ScopeService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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, } } @@ -543,11 +551,11 @@ impl Future for ScopeFactoryResponse { ref mut path, ref mut guards, ref mut fut, - ) => match fut.poll()? { - Async::Ready(service) => { + ) => match Pin::new(fut).poll(cx)? { + Poll::Ready(service) => { Some((path.take().unwrap(), guards.take(), service)) } - Async::NotReady => { + Poll::Pending => { done = false; None } @@ -573,14 +581,14 @@ impl Future for ScopeFactoryResponse { } router }); - Ok(Async::Ready(ScopeService { + Poll::Ready(Ok(ScopeService { data: self.data.clone(), router: router.finish(), default: self.default.take(), _ready: None, })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -596,10 +604,10 @@ impl Service for ScopeService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either>; + type Future = Either>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { @@ -618,12 +626,12 @@ impl Service for ScopeService { if let Some(ref data) = self.data { req.set_data_container(data.clone()); } - Either::A(srv.call(req)) + Either::Left(srv.call(req)) } else if let Some(ref mut default) = self.default { - Either::A(default.call(req)) + Either::Left(default.call(req)) } else { let req = req.into_parts().0; - Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + Either::Right(ok(ServiceResponse::new(req, Response::NotFound().finish()))) } } } @@ -639,7 +647,7 @@ impl ScopeEndpoint { } } -impl NewService for ScopeEndpoint { +impl ServiceFactory for ScopeEndpoint { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -657,367 +665,416 @@ impl NewService for ScopeEndpoint { mod tests { use actix_service::Service; use bytes::Bytes; - use futures::{Future, IntoFuture}; + use futures::future::ok; + use futures::Future; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::middleware::DefaultHeaders; use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_scope_root2() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), - )); + block_on(async { + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), + )) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_root3() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), - )); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app/") + .service(web::resource("/").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_scope_route() { - let mut srv = init_service( - App::new().service( - web::scope("app") - .route("/path1", web::get().to(|| HttpResponse::Ok())) - .route("/path1", web::delete().to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app") + .route("/path1", web::get().to(|| HttpResponse::Ok())) + .route("/path1", web::delete().to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_scope_route_without_leading_slash() { - let mut srv = init_service( - App::new().service( - web::scope("app").service( - web::resource("path1") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app").service( + web::resource("path1") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + }) } #[test] fn test_scope_guard() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .guard(guard::Get()) + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_variable_segment() { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }), - ))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))) + .await; - let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/ab-project1/path1").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"project: project1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project1")); + } + _ => panic!(), } - _ => panic!(), - } - let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/aa-project1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_nested_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("/t1").service( + block_on(async { + let mut srv = + init_service(App::new().service( + web::scope("/app").service(web::scope("/t1").service( web::resource("/path1").to(|| HttpResponse::Created()), )), - ), - ); + )) + .await; - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_no_slash() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("t1").service( + block_on(async { + let mut srv = + init_service(App::new().service( + web::scope("/app").service(web::scope("t1").service( web::resource("/path1").to(|| HttpResponse::Created()), )), - ), - ); + )) + .await; - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/t1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/t1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_filter() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .guard(guard::Get()) + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project_id}").service(web::resource("/path1").to( - |r: HttpRequest| { - HttpResponse::Created() - .body(format!("project: {}", &r.match_info()["project_id"])) - }, - )), - ))); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project_id}").service(web::resource("/path1").to( + |r: HttpRequest| { + HttpResponse::Created() + .body(format!("project: {}", &r.match_info()["project_id"])) + }, + )), + ))) + .await; - let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/project_1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + } + _ => panic!(), } - _ => panic!(), - } + }) } #[test] fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project}").service(web::scope("/{id}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }), - )), - ))); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project}").service(web::scope("/{id}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }), + )), + ))) + .await; - let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/test/1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + } + _ => panic!(), } - _ => panic!(), - } - let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/test/1/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_default_resource() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::BadRequest()) - }), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::BadRequest())) + }), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/app/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_default_resource_propagation() { - let mut srv = init_service( - App::new() - .service(web::scope("/app1").default_service( - web::resource("").to(|| HttpResponse::BadRequest()), - )) - .service(web::scope("/app2")) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::MethodNotAllowed()) - }), - ); + block_on(async { + let mut srv = init_service( + App::new() + .service(web::scope("/app1").default_service( + web::resource("").to(|| HttpResponse::BadRequest()), + )) + .service(web::scope("/app2")) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::MethodNotAllowed())) + }), + ) + .await; - let req = TestRequest::with_uri("/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/app1/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/app1/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/app2/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/app2/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + }) } fn md( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, Error>> where S: Service< Request = ServiceRequest, @@ -1025,165 +1082,207 @@ 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::scope("app").wrap(md).service( - web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), - ))); - let req = TestRequest::with_uri("/app/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::scope("app") + .wrap(DefaultHeaders::new().header( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + )) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/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::scope("app") - .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::scope("app") + .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) + } }) - }) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ), - ); - let req = TestRequest::with_uri("/app/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("/test", web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/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_override_data() { - let mut srv = init_service(App::new().data(1usize).service( - web::scope("app").data(10usize).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - )); + block_on(async { + let mut srv = init_service(App::new().data(1usize).service( + web::scope("app").data(10usize).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )) + .await; - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_override_register_data() { - let mut srv = init_service( - App::new().register_data(web::Data::new(1usize)).service( - web::scope("app") - .register_data(web::Data::new(10usize)) - .route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().register_data(web::Data::new(1usize)).service( + web::scope("app") + .register_data(web::Data::new(10usize)) + .route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + ), + ) + .await; - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_config() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(|| HttpResponse::Ok())); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(|| HttpResponse::Ok())); + }))) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_config_2() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(|| HttpResponse::Ok())); - })); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(|| HttpResponse::Ok())); + })); + }))) + .await; - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_url_for_external() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["xxxxxx"]).unwrap().as_str() - )) - }), - ); - })); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]) + .unwrap() + .as_str() + )) + }), + ); + })); + }))) + .await; - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + }) } #[test] fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( - web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( - web::get().to(|req: HttpRequest| { - HttpResponse::Ok() - .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) - }), - )), - ))); - let req = TestRequest::with_uri("/a/b/c/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"http://localhost:8080/a/b/c/12345") - ); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/a").service( + web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( + web::get().to(|req: HttpRequest| { + HttpResponse::Ok() + .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + }), + )), + ))) + .await; + + let req = TestRequest::with_uri("/a/b/c/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"http://localhost:8080/a/b/c/12345") + ); + }) } } diff --git a/src/server.rs b/src/server.rs index 51492eb01..a98d06275 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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 where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, - S::Service: 'static, B: MessageBody, { pub(super) factory: F, @@ -71,12 +70,12 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, - S::Error: Into, + I: IntoServiceFactory, + S: ServiceFactory, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - S::Service: 'static, + S::Response: Into> + 'static, + ::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 { 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 { 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( + pub fn bind_openssl( 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 HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -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 { - use openssl::ssl::AlpnError; + use open_ssl::ssl::AlpnError; builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; diff --git a/src/service.rs b/src/service.rs index 8b94dd284..9c4e6b4aa 100644 --- a/src/service.rs +++ b/src/service.rs @@ -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 ServiceFactoryWrapper { } } -impl ServiceFactory for ServiceFactoryWrapper +impl AppServiceFactory for ServiceFactoryWrapper where T: HttpServiceFactory, { @@ -404,16 +404,6 @@ impl Into> for ServiceResponse { } } -impl IntoFuture for ServiceResponse { - type Item = ServiceResponse; - type Error = Error; - type Future = FutureResult, Error>; - - fn into_future(self) -> Self::Future { - ok(self) - } -} - impl fmt::Debug for ServiceResponse { 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> { + /// 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(self, service: F) -> impl HttpServiceFactory where - F: IntoNewService, - T: NewService< + F: IntoServiceFactory, + 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 { impl HttpServiceFactory for WebServiceImpl 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] diff --git a/src/test.rs b/src/test.rs index 6563253cc..8cee3bc6a 100644 --- a/src/test.rs +++ b/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, 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( +pub async fn init_service( app: R, ) -> impl Service, Error = E> where - R: IntoNewService, - S: NewService< + R: IntoServiceFactory, + S: ServiceFactory< Config = ServerConfig, Request = Request, Response = ServiceResponse, @@ -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(app: &mut S, req: R) -> S::Response +pub async fn call_service(app: &mut S, req: R) -> S::Response where S: Service, 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(app: &mut S, req: Request) -> Bytes +pub async fn read_response(app: &mut S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody, { - block_on(run_on(move || { - app.call(req).and_then(|mut resp: ServiceResponse| { - 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(mut res: ServiceResponse) -> Bytes +pub async fn read_body(mut res: ServiceResponse) -> 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(mut stream: S) -> Result +where + S: Stream> + 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(app: &mut S, req: Request) -> T +pub async fn read_response_json(app: &mut S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody, T: DeserializeOwned, { - block_on(run_on(move || { - app.call(req).and_then(|mut resp: ServiceResponse| { - 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::().unwrap(); - assert!(req.get_app_data::().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::().unwrap(); + assert!(req.get_app_data::().is_none()); + assert_eq!(*data, 10); + assert_eq!(*data.get_ref(), 10); - assert!(req.app_data::().is_none()); - let data = req.app_data::().unwrap(); - assert_eq!(*data, 10); + assert!(req.app_data::().is_none()); + let data = req.app_data::().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| { - 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| { + 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| { - 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| { + 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| { - 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| { + 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 { - 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 { + 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; - } - impl actix::Handler 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; + // } + // impl actix::Handler 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()); + // } } diff --git a/src/types/form.rs b/src/types/form.rs index c727ce0e5..694fe6dbd 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -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>; + type Future = LocalBoxFuture<'static, Result>; #[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 fmt::Display for Form { impl Responder for Form { type Error = Error; - type Future = Result; + type Future = Ready>; 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 { length: Option, encoding: &'static Encoding, err: Option, - fut: Option>>, + fut: Option>>, } impl UrlEncoded { @@ -301,45 +306,45 @@ impl Future for UrlEncoded where U: DeserializeOwned + 'static, { - type Item = U; - type Error = UrlencodedError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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::(&body) .map_err(|_| UrlencodedError::Parse) @@ -351,9 +356,10 @@ where serde_urlencoded::from_str::(&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::::from_request(&req, &mut pl)).unwrap(); - assert_eq!( - s, - Info { - hello: "world".into(), - counter: 123 - } - ); + let Form(s) = Form::::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::::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::::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::::new(&req, &mut pl)); - assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); + let info = UrlEncoded::::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::::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::::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::::new(&req, &mut pl)).unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); + let info = UrlEncoded::::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::::new(&req, &mut pl)).unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); + let info = UrlEncoded::::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"); + }) } } diff --git a/src/types/json.rs b/src/types/json.rs index e80d0a45f..19f8532bd 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -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 Responder for Json { type Error = Error; - type Future = Result; + type Future = Ready>; 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>; + type Future = LocalBoxFuture<'static, Result>; 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 { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl JsonBody @@ -349,41 +354,43 @@ impl Future for JsonBody where U: DeserializeOwned + 'static, { - type Item = U; - type Error = JsonPayloadError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() + Ok(serde_json::from_slice::(&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::::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::::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::::from_request(&req, &mut pl)).unwrap(); - assert_eq!(s.name, "test"); - assert_eq!( - s.into_inner(), - MyObject { - name: "test".to_string() - } - ); + let s = Json::::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::::from_request(&req, &mut pl)); - assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed")); + let s = Json::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::from_request(&req, &mut pl)); - assert!(s.is_err()) + let s = Json::::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::::from_request(&req, &mut pl)); - assert!(s.is_ok()) + let s = Json::::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::::from_request(&req, &mut pl)); - assert!(s.is_err()) + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_err()) + }) } } diff --git a/src/types/path.rs b/src/types/path.rs index fa7c6e110..89b9392be 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -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; + type Future = Ready>; 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::::from_request(&req, &mut pl).unwrap(), 32); - assert!(Path::::from_request(&req, &mut pl).is_err()); + let (req, mut pl) = req.into_parts(); + assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); + assert!(Path::::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::::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::::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::::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::::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::>::from_request(&req, &mut pl).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); + let res = Path::>::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); + }) } } diff --git a/src/types/payload.rs b/src/types/payload.rs index 8fc5f093e..61f7328b4 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -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 +/// async fn index(mut body: web::Payload) -> Result /// { -/// 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; #[inline] - fn poll(&mut self) -> Poll, PayloadError> { - self.0.poll() + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { + 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 +/// async fn index(mut body: web::Payload) -> Result /// { -/// 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; + type Future = Ready>; #[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>, FutureResult>; + type Future = Either< + LocalBoxFuture<'static, Result>, + Ready>, + >; #[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>, - FutureResult, + LocalBoxFuture<'static, Result>, + Ready>, >; #[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, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl HttpMessageBody { @@ -346,42 +349,43 @@ impl HttpMessageBody { } impl Future for HttpMessageBody { - type Item = Bytes; - type Error = PayloadError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 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) } } diff --git a/src/types/query.rs b/src/types/query.rs index 817b2ed7b..8061d7233 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -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; + type Future = Ready>; type Config = QueryConfig; #[inline] @@ -143,7 +144,7 @@ where .unwrap_or(None); serde_urlencoded::from_str::(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::::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::::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::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + let mut s = Query::::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::::from_request(&req, &mut pl); + let (req, mut pl) = req.into_parts(); + let query = Query::::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 + ); + }) } } diff --git a/src/types/readlines.rs b/src/types/readlines.rs index cea63e43b..e2b3f9c1d 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -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 { impl Readlines where T: HttpMessage, - T::Stream: Stream, + T::Stream: Stream> + Unpin, { /// Create a new stream to read request line by line. pub fn new(req: &mut T) -> Self { @@ -62,20 +66,21 @@ where impl Stream for Readlines where T: HttpMessage, - T::Stream: Stream, + T::Stream: Stream> + Unpin, { - type Item = String; - type Error = ReadlinesError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.err.take() { - return Err(err); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + 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 = 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 = 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." + ); + }) } } diff --git a/src/web.rs b/src/web.rs index 5669a1e86..67cfd51a2 100644 --- a/src/web.rs +++ b/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 { -/// ok(HttpResponse::Ok().finish()) +/// async fn index() -> Result { +/// Ok(HttpResponse::Ok().finish()) /// } /// /// App::new().service(web::resource("/").route( /// web::to_async(index)) /// ); /// ``` -pub fn to_async(handler: F) -> Route +pub fn to_async(handler: F) -> Route where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + '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> { +/// 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: F) -> impl Future> +pub fn block(f: F) -> impl Future> where - F: FnOnce() -> Result + 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) } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 3333e0486..e43820290 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -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" } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index bf6558b51..0c24ac907 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -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; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index c0d2e81c4..122f79baf 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -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 { - 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 { } #[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); diff --git a/tests/test_server.rs b/tests/test_server.rs index 1623d2ef3..eeaedec05 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,18 +1,17 @@ use std::io::{Read, Write}; -use std::sync::mpsc; -use std::thread; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use actix_http::{h1, Error, HttpService, Response}; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; use flate2::Compression; +use futures::future::ok; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; @@ -44,679 +43,721 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), - ) - }); + block_on(async { + let srv = + TestServer::start(|| { + h1::H1Service::new(App::new().service( + web::resource("/").route(web::to(|| Response::Ok().body(STR))), + )) + }); - let mut response = srv.block_on(srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), - ) - }); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/").route(web::to(|| Response::Ok().body(STR))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip2() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().body(STR).into_body::() - }))), - ) - }); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().body(STR).into_body::() + }))), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_encoding_override() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().encoding(ContentEncoding::Deflate).body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = - Response::with_body(actix_web::http::StatusCode::OK, body); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().encoding(ContentEncoding::Deflate).body(STR) + }))) + .service(web::resource("/raw").route(web::to(|| { + let body = actix_web::dev::Body::Bytes(STR.into()); + let mut response = + Response::with_body(actix_web::http::StatusCode::OK, body); - response.encoding(ContentEncoding::Deflate); + response.encoding(ContentEncoding::Deflate); - response - }))), - ) - }); + response + }))), + ) + }); - // Builder - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // Builder + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - // Raw Response - let mut response = srv - .block_on( - srv.request(actix_web::http::Method::GET, srv.url("/raw")) - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // Raw Response + let mut response = srv + .request(actix_web::http::Method::GET, srv.url("/raw")) + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = data.clone(); + block_on(async { + let data = STR.repeat(10); + let srv_data = data.clone(); - let mut srv = TestServer::new(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from(data)); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = data.clone(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(70_000) + .collect::(); + let srv_data = data.clone(); - let mut srv = TestServer::new(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(dec.len(), data.len()); + assert_eq!(Bytes::from(dec), Bytes::from(data)); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_chunked_implicit() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::get().to(move || { - Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - }))), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::get().to(move || { + Response::Ok().streaming(once(ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + }))), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response.headers().get(TRANSFER_ENCODING).unwrap(), - &b"chunked"[..] - ); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response.headers().get(TRANSFER_ENCODING).unwrap(), + &b"chunked"[..] + ); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(feature = "brotli")] fn test_body_br_streaming() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { - Response::Ok() - .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - })), - )) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || { + Response::Ok().streaming(once(ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + })), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode br + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_head_binary() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().service(web::resource("/").route( - web::head().to(move || Response::Ok().content_length(100).body(STR)), - ))) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route( + web::head().to(move || Response::Ok().content_length(100).body(STR)), + ))) + }); - let mut response = srv.block_on(srv.head("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response.headers().get(CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response.headers().get(CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert!(bytes.is_empty()); + // read response + let bytes = response.body().await.unwrap(); + assert!(bytes.is_empty()); + }) } #[test] fn test_no_chunking() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().service(web::resource("/").route(web::to( - move || { - Response::Ok() - .no_chunking() - .content_length(STR.len() as u64) - .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - }, - )))) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route(web::to( + move || { + Response::Ok() + .no_chunking() + .content_length(STR.len() as u64) + .streaming(once(ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }, + )))) + }); - let mut response = srv.block_on(srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(TRANSFER_ENCODING)); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(TRANSFER_ENCODING)); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_body_deflate() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Deflate)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(STR))), + ), + ) + }); - // client request - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "deflate") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "deflate") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "brotli"))] fn test_body_brotli() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - )) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), + ) + }); - // client request - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode brotli + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_encoding() { - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + HttpService::new( + App::new().wrap(Compress::default()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding() { - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(60_000) + .collect::(); - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(feature = "brotli")] fn test_brotli_encoding() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[cfg(feature = "brotli")] #[test] fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } // #[cfg(all(feature = "brotli", feature = "ssl"))] @@ -782,85 +823,87 @@ fn test_brotli_encoding_large() { ))] #[test] fn test_reading_deflate_encoding_large_random_ssl() { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; + block_on(async { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; - let addr = TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); + let addr = TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - thread::spawn(move || { - let sys = actix_rt::System::new("test"); + thread::spawn(move || { + let sys = actix_rt::System::new("test"); - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - Ok::<_, Error>( - HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) - .body(bytes), + let srv = HttpServer::new(|| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + Ok::<_, Error>( + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes), + ) + }))) + }) + .bind_rustls(addr, config) + .unwrap() + .start(); + + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, _sys) = rx.recv().unwrap(); + test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); + let client = test::run_on(|| { + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); + + awc::Client::build() + .connector( + awc::Connector::new() + .timeout(std::time::Duration::from_millis(500)) + .ssl(builder.build()) + .finish(), ) - }))) - }) - .bind_rustls(addr, config) - .unwrap() - .start(); + .finish() + }); - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); - }); - let (srv, _sys) = rx.recv().unwrap(); - test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); - let client = test::run_on(|| { - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - awc::Client::build() - .connector( - awc::Connector::new() - .timeout(std::time::Duration::from_millis(500)) - .ssl(builder.build()) - .finish(), - ) - .finish() - }); + // client request + let req = client + .post(format!("https://localhost:{}/", addr.port())) + .header(http::header::CONTENT_ENCODING, "deflate") + .send_body(enc); - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut response = test::block_on(req).unwrap(); + assert!(response.status().is_success()); - // client request - let req = client - .post(format!("https://localhost:{}/", addr.port())) - .header(http::header::CONTENT_ENCODING, "deflate") - .send_body(enc); + // read response + let bytes = test::block_on(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); - let mut response = test::block_on(req).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = test::block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - // stop - let _ = srv.stop(false); + // stop + let _ = srv.stop(false); + }) } // #[cfg(all(feature = "tls", feature = "ssl"))] @@ -954,7 +997,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { // fn test_server_cookies() { // use actix_web::http; -// let mut srv = test::TestServer::with_factory(|| { +// let srv = test::TestServer::with_factory(|| { // App::new().resource("/", |r| { // r.f(|_| { // HttpResponse::Ok()