From bcc60b091d598c9b8664c67b9c1129689d2c351f Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 5 Feb 2021 14:24:18 -0800 Subject: [PATCH] squash commits --- .github/PULL_REQUEST_TEMPLATE.md | 10 +- CHANGES.md | 14 + Cargo.toml | 15 +- actix-files/Cargo.toml | 2 +- actix-files/src/lib.rs | 174 +++-- actix-files/src/named.rs | 31 +- actix-files/src/service.rs | 21 +- actix-files/tests/encoding.rs | 9 +- actix-http-test/Cargo.toml | 2 +- actix-http-test/src/lib.rs | 5 +- actix-http/CHANGES.md | 31 +- actix-http/Cargo.toml | 13 +- actix-http/examples/echo.rs | 5 +- actix-http/examples/echo2.rs | 2 +- actix-http/examples/hello-world.rs | 5 +- actix-http/src/client/config.rs | 7 +- actix-http/src/client/connection.rs | 3 +- actix-http/src/client/connector.rs | 14 +- actix-http/src/client/error.rs | 3 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/client/pool.rs | 29 +- actix-http/src/config.rs | 2 +- actix-http/src/error.rs | 1 - actix-http/src/extensions.rs | 101 ++- actix-http/src/h1/decoder.rs | 27 +- actix-http/src/h1/dispatcher.rs | 687 +++++++++--------- actix-http/src/h1/expect.rs | 8 +- actix-http/src/h1/service.rs | 16 +- actix-http/src/h1/upgrade.rs | 8 +- actix-http/src/h2/dispatcher.rs | 9 +- actix-http/src/h2/service.rs | 11 +- actix-http/src/header/common/accept.rs | 29 +- .../src/header/common/accept_charset.rs | 23 +- .../src/header/common/accept_language.rs | 20 +- actix-http/src/header/common/allow.rs | 22 +- actix-http/src/header/common/cache_control.rs | 31 +- .../src/header/common/content_disposition.rs | 16 +- .../src/header/common/content_encoding.rs | 106 +++ .../src/header/common/content_language.rs | 25 +- actix-http/src/header/common/content_range.rs | 2 +- actix-http/src/header/common/content_type.rs | 18 +- actix-http/src/header/common/date.rs | 8 +- actix-http/src/header/common/etag.rs | 12 +- actix-http/src/header/common/expires.rs | 8 +- actix-http/src/header/common/if_match.rs | 8 +- .../src/header/common/if_modified_since.rs | 8 +- actix-http/src/header/common/if_none_match.rs | 18 +- actix-http/src/header/common/if_range.rs | 30 +- .../src/header/common/if_unmodified_since.rs | 8 +- actix-http/src/header/common/last_modified.rs | 8 +- actix-http/src/header/common/mod.rs | 18 +- actix-http/src/header/into_pair.rs | 117 +++ actix-http/src/header/into_value.rs | 131 ++++ actix-http/src/header/map.rs | 46 +- actix-http/src/header/mod.rs | 429 +---------- actix-http/src/header/shared/entity.rs | 2 +- actix-http/src/header/shared/extended.rs | 193 +++++ actix-http/src/header/shared/httpdate.rs | 2 +- actix-http/src/header/shared/mod.rs | 18 +- actix-http/src/header/utils.rs | 63 ++ actix-http/src/httpmessage.rs | 71 +- actix-http/src/lib.rs | 4 +- actix-http/src/request.rs | 8 +- actix-http/src/response.rs | 268 ++++--- actix-http/src/service.rs | 24 +- actix-http/src/test.rs | 99 ++- actix-http/src/ws/mask.rs | 8 +- actix-http/src/ws/mod.rs | 66 +- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 14 +- actix-http/tests/test_rustls.rs | 8 +- actix-http/tests/test_server.rs | 12 +- actix-http/tests/test_ws.rs | 9 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 4 +- actix-web-actors/src/context.rs | 11 +- actix-web-actors/src/ws.rs | 118 +-- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 3 +- actix-web-codegen/tests/test_macro.rs | 10 +- awc/CHANGES.md | 15 + awc/Cargo.toml | 7 +- awc/README.md | 2 +- awc/src/builder.rs | 11 +- awc/src/connect.rs | 16 +- awc/src/frozen.rs | 2 +- awc/src/lib.rs | 19 +- awc/src/request.rs | 160 ++-- awc/src/sender.rs | 14 +- awc/src/test.rs | 4 +- awc/src/ws.rs | 12 +- awc/tests/test_client.rs | 257 +++---- awc/tests/test_rustls_client.rs | 76 +- benches/responder.rs | 4 +- benches/server.rs | 2 +- benches/service.rs | 4 +- examples/client.rs | 2 +- src/app.rs | 57 +- src/app_service.rs | 29 +- src/config.rs | 98 ++- src/data.rs | 43 +- src/extract.rs | 58 +- src/guard.rs | 11 +- src/handler.rs | 6 +- src/info.rs | 12 +- src/lib.rs | 2 +- src/middleware/compat.rs | 19 +- src/middleware/compress.rs | 5 +- src/middleware/condition.rs | 12 +- src/middleware/default_headers.rs | 15 +- src/middleware/err_handlers.rs | 10 +- src/middleware/logger.rs | 75 +- src/middleware/normalize.rs | 56 +- src/request.rs | 50 +- src/request_data.rs | 4 +- src/resource.rs | 69 +- src/responder.rs | 78 +- src/route.rs | 22 +- src/scope.rs | 153 ++-- src/server.rs | 2 +- src/service.rs | 16 +- src/test.rs | 161 ++-- src/types/form.rs | 60 +- src/types/json.rs | 137 ++-- src/types/payload.rs | 74 +- tests/test_httpserver.rs | 4 +- tests/test_server.rs | 164 ++--- tests/test_weird_poll.rs | 52 +- 129 files changed, 2930 insertions(+), 2662 deletions(-) create mode 100644 actix-http/src/header/common/content_encoding.rs create mode 100644 actix-http/src/header/into_pair.rs create mode 100644 actix-http/src/header/into_value.rs create mode 100644 actix-http/src/header/shared/extended.rs create mode 100644 actix-http/src/header/utils.rs diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b779b33fa..42deadf5a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,21 @@ - + ## PR Type -INSERT_PR_TYPE +PR_TYPE ## PR Checklist -Check your PR fulfills the following: - + - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] A changelog entry has been made for the appropriate packages. -- [ ] Format code with the latest stable rustfmt +- [ ] Format code with the latest stable rustfmt. +- [ ] (Team) Label with affected crates and semver status. ## Overview diff --git a/CHANGES.md b/CHANGES.md index 00608df76..e60513a00 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,14 +11,28 @@ * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` + in argument [#1905] +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure + argument [#1905] + +### Fixed +* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] ### Removed * Public field of `web::Path` has been made private. [#1894] * Public field of `web::Query` has been made private. [#1894] +* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +* `AppService::set_service_data`; for custom HTTP service factories adding application data, use the + layered data model by calling `ServiceRequest::add_data_container` when handling + requests instead. [#1906] [#1891]: https://github.com/actix/actix-web/pull/1891 [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1906]: https://github.com/actix/actix-web/pull/1906 ## 4.0.0-beta.1 - 2021-01-07 diff --git a/Cargo.toml b/Cargo.toml index bae6cb6cb..e92615474 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,9 +74,9 @@ required-features = ["rustls"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-macros = "0.1.0" -actix-router = "0.2.4" -actix-rt = "2.0.0-beta.2" +actix-macros = "0.2.0" +actix-router = "0.2.6" +actix-rt = "2" actix-server = "2.0.0-beta.2" actix-service = "2.0.0-beta.3" actix-utils = "3.0.0-beta.1" @@ -108,7 +108,7 @@ rust-tls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.1", default-features = false } actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" @@ -132,6 +132,13 @@ actix-multipart = { path = "actix-multipart" } actix-files = { path = "actix-files" } awc = { path = "awc" } +actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-server = { git = "https://github.com/actix/actix-net.git" } +actix-tls = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-router = { git = "https://github.com/actix/actix-net.git" } +actix = { git = "https://github.com/actix/actix.git" } + [[bench]] name = "server" harness = false diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bde2cb717..9889ea813 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index f4314f6bc..b2f6384a8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -73,7 +73,8 @@ mod tests { }, middleware::Compress, test::{self, TestRequest}, - web, App, HttpResponse, Responder, + web::{self, Bytes}, + App, HttpResponse, Responder, }; use futures_util::future::ok; @@ -101,7 +102,7 @@ mod tests { header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); @@ -113,7 +114,7 @@ mod tests { let since = file.last_modified().unwrap(); let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); @@ -126,8 +127,8 @@ mod tests { header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_NONE_MATCH, "miss_etag")) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); @@ -139,7 +140,7 @@ mod tests { let since = file.last_modified().unwrap(); let req = TestRequest::default() - .header(header::IF_UNMODIFIED_SINCE, since) + .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -151,7 +152,7 @@ mod tests { let since = header::HttpDate::from(SystemTime::UNIX_EPOCH); let req = TestRequest::default() - .header(header::IF_UNMODIFIED_SINCE, since) + .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED); @@ -365,7 +366,7 @@ mod tests { DispositionType::Attachment } - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .mime_override(all_attachment) @@ -375,7 +376,7 @@ mod tests { .await; let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let content_disposition = response @@ -390,7 +391,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_ranges_status_code() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), ) .await; @@ -398,17 +399,17 @@ mod tests { // Valid range header let request = TestRequest::get() .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); // Invalid range header let request = TestRequest::get() .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=1-0") + .insert_header((header::RANGE, "bytes=1-0")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } @@ -420,7 +421,7 @@ mod tests { // Valid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .send() .await .unwrap(); @@ -430,7 +431,7 @@ mod tests { // Invalid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-5") + .insert_header((header::RANGE, "bytes=10-5")) .send() .await .unwrap(); @@ -445,7 +446,7 @@ mod tests { // Valid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .send() .await .unwrap(); @@ -455,7 +456,7 @@ mod tests { // Valid range header, starting from 0 let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=0-20") + .insert_header((header::RANGE, "bytes=0-20")) .send() .await .unwrap(); @@ -495,14 +496,14 @@ mod tests { #[actix_rt::test] async fn test_static_files_with_spaces() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").index_file("Cargo.toml")), ) .await; let request = TestRequest::get() .uri("/tests/test%20space.binary") .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let bytes = test::read_body(response).await; @@ -512,28 +513,28 @@ mod tests { #[actix_rt::test] async fn test_files_not_allowed() { - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .uri("/Cargo.toml") .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .method(Method::PUT) .uri("/Cargo.toml") .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[actix_rt::test] async fn test_files_guards() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").use_guards(guard::Post())), ) .await; @@ -543,13 +544,13 @@ mod tests { .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -560,16 +561,16 @@ mod tests { let request = TestRequest::get() .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") + .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); } #[actix_rt::test] async fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -580,9 +581,9 @@ mod tests { let request = TestRequest::get() .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") + .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers() @@ -604,27 +605,27 @@ mod tests { #[actix_rt::test] async fn test_static_files() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/missing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8" @@ -637,16 +638,16 @@ mod tests { #[actix_rt::test] async fn test_redirect_to_slash_directory() { // should not redirect if no index - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").redirect_to_slash_directory()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); // should redirect if index present - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .index_file("test.png") @@ -655,12 +656,12 @@ mod tests { ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::FOUND); // should not redirect if the path is wrong let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); } @@ -672,7 +673,7 @@ mod tests { #[actix_rt::test] async fn test_default_handler_file_missing() { - let mut st = Files::new("/", ".") + let st = Files::new("/", ".") .default_handler(|req: ServiceRequest| { ok(req.into_response(HttpResponse::Ok().body("default content"))) }) @@ -681,7 +682,7 @@ mod tests { .unwrap(); let req = TestRequest::with_uri("/missing").to_srv_request(); - let resp = test::call_service(&mut st, req).await; + let resp = test::call_service(&st, req).await; assert_eq!(resp.status(), StatusCode::OK); let bytes = test::read_body(resp).await; assert_eq!(bytes, web::Bytes::from_static(b"default content")); @@ -750,54 +751,49 @@ mod tests { // ); // } - // #[actix_rt::test] - // fn integration_serve_index() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + #[actix_rt::test] + async fn integration_serve_index() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; - // let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let req = TestRequest::get().uri("/test").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let bytes = test::read_body(res).await; - // // nonexistent index file - // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); - // } + let req = TestRequest::get().uri("/test/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // #[actix_rt::test] - // fn integration_percent_encoded() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv - // .get() - // .uri(srv.url("/test/%43argo.toml")) - // .finish() - // .unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // } + // nonexistent index file + let req = TestRequest::get().uri("/test/unknown").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::get().uri("/test/unknown/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn integration_percent_encoded() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; + + let req = TestRequest::get().uri("/test/%43argo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index dc461e29a..8cd2a23f9 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -282,16 +282,16 @@ impl NamedFile { if self.flags.contains(Flags::PREFER_UTF8) { let ct = equiv_utf8_text(self.content_type.clone()); - res.header(header::CONTENT_TYPE, ct.to_string()); + res.insert_header((header::CONTENT_TYPE, ct.to_string())); } else { - res.header(header::CONTENT_TYPE, self.content_type.to_string()); + res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); } if self.flags.contains(Flags::CONTENT_DISPOSITION) { - res.header( + res.insert_header(( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), - ); + )); } if let Some(current_encoding) = self.encoding { @@ -361,16 +361,16 @@ impl NamedFile { if self.flags.contains(Flags::PREFER_UTF8) { let ct = equiv_utf8_text(self.content_type.clone()); - resp.header(header::CONTENT_TYPE, ct.to_string()); + resp.insert_header((header::CONTENT_TYPE, ct.to_string())); } else { - resp.header(header::CONTENT_TYPE, self.content_type.to_string()); + resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); } if self.flags.contains(Flags::CONTENT_DISPOSITION) { - resp.header( + resp.insert_header(( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), - ); + )); } // default compressing @@ -379,14 +379,14 @@ impl NamedFile { } if let Some(lm) = last_modified { - resp.header(header::LAST_MODIFIED, lm.to_string()); + resp.insert_header((header::LAST_MODIFIED, lm.to_string())); } if let Some(etag) = etag { - resp.header(header::ETAG, etag.to_string()); + resp.insert_header((header::ETAG, etag.to_string())); } - resp.header(header::ACCEPT_RANGES, "bytes"); + resp.insert_header((header::ACCEPT_RANGES, "bytes")); let mut length = self.md.len(); let mut offset = 0; @@ -399,7 +399,7 @@ impl NamedFile { offset = ranges[0].start; resp.encoding(ContentEncoding::Identity); - resp.header( + resp.insert_header(( header::CONTENT_RANGE, format!( "bytes {}-{}/{}", @@ -407,9 +407,12 @@ impl NamedFile { offset + length - 1, self.md.len() ), - ); + )); } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); + resp.insert_header(( + header::CONTENT_RANGE, + format!("bytes */{}", length), + )); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 05431db38..a9822f486 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,9 +1,4 @@ -use std::{ - fmt, io, - path::PathBuf, - rc::Rc, - task::{Context, Poll}, -}; +use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll}; use actix_service::Service; use actix_web::{ @@ -40,10 +35,10 @@ type FilesServiceFuture = Either< >; impl FilesService { - fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { + fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { log::debug!("Failed to handle {}: {}", req.path(), e); - if let Some(ref mut default) = self.default { + if let Some(ref default) = self.default { Either::Right(default.call(req)) } else { Either::Left(ok(req.error_response(e))) @@ -62,11 +57,9 @@ impl Service for FilesService { type Error = Error; type Future = FilesServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let is_method_valid = if let Some(guard) = &self.guards { // execute user defined guards (**guard).check(req.head()) @@ -78,7 +71,7 @@ impl Service for FilesService { if !is_method_valid { return Either::Left(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") + .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .body("Request did not meet this resource's requirements."), ))); } @@ -102,7 +95,7 @@ impl Service for FilesService { return Either::Left(ok(req.into_response( HttpResponse::Found() - .header(header::LOCATION, redirect_to) + .insert_header((header::LOCATION, redirect_to)) .body("") .into_body(), ))); diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index d7e01b305..6cfa3a7f7 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -11,11 +11,10 @@ use actix_web::{ #[actix_rt::test] async fn test_utf8_file_contents() { // use default ISO-8859-1 encoding - let mut srv = - test::init_service(App::new().service(Files::new("/", "./tests"))).await; + let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( @@ -24,13 +23,13 @@ async fn test_utf8_file_contents() { ); // prefer UTF-8 encoding - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", "./tests").prefer_utf8(true)), ) .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 772b60f76..b678c4fcc 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.2" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "2" actix-server = "2.0.0-beta.2" awc = "3.0.0-beta.1" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 4fd74d6eb..2958b7f59 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -60,7 +60,7 @@ pub async fn test_server_with_addr>( // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + let sys = System::new(); let local_addr = tcp.local_addr().unwrap(); let srv = Server::build() @@ -69,7 +69,7 @@ pub async fn test_server_with_addr>( .disable_signals(); sys.block_on(async { - srv.start(); + srv.run(); tx.send((System::current(), local_addr)).unwrap(); }); @@ -106,7 +106,6 @@ pub async fn test_server_with_addr>( Client::builder().connector(connector).finish() }; - actix_tls::connect::start_default_resolver().await.unwrap(); TestServer { addr, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e9a94300b..d46b56983 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,37 @@ # Changes ## Unreleased - 2021-xx-xx -* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894] +### Added +* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] +* `ResponseBuilder::append_header` method which allows using typed headers. [#1869] +* `TestRequest::insert_header` method which allows using typed headers. [#1869] +* `ContentEncoding` implements all necessary header traits. [#1912] +### Changed +* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed + `mime` types. [#1894] +* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std + `TryInto` trait. [#1894] +* `Extensions::insert` returns Option of replaced item. [#1904] +* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by + reference. [#1903] +* `client::error::ConnectError` Resolver variant contains `Box` type [#1905] +* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] + +### Removed +* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] +* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] +* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] + +[#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 +[#1903]: https://github.com/actix/actix-web/pull/1903 +[#1904]: https://github.com/actix/actix-web/pull/1904 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1912]: https://github.com/actix/actix-web/pull/1912 ## 3.0.0-beta.1 - 2021-01-07 @@ -34,6 +62,7 @@ [#1864]: https://github.com/actix/actix-web/pull/1864 [#1878]: https://github.com/actix/actix-web/pull/1878 + ## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0cc8e5cf9..8d140dfd1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -43,9 +43,9 @@ actors = ["actix"] actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "2" actix-tls = "3.0.0-beta.2" -actix = { version = "0.11.0-beta.1", optional = true } +actix = { version = "0.11.0-beta.1", default-features = false, optional = true } base64 = "0.13" bitflags = "1.2" @@ -55,10 +55,10 @@ cookie = { version = "0.14.1", features = ["percent-encode"] } derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" -futures-channel = { version = "0.3.7", default-features = false } -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } -fxhash = "0.2.1" +futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } +ahash = "0.6" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" @@ -75,6 +75,7 @@ regex = "1.3" serde = "1.0" serde_json = "1.0" sha-1 = "0.9" +smallvec = "1.6" slab = "0.4" serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index beb1cce2c..90d768cbe 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -26,7 +26,10 @@ async fn main() -> io::Result<()> { info!("request body: {:?}", body); Ok::<_, Error>( Response::Ok() - .header("x-head", HeaderValue::from_static("dummy value!")) + .insert_header(( + "x-head", + HeaderValue::from_static("dummy value!"), + )) .body(body), ) }) diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 5b7e504d3..bc932ce8f 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -15,7 +15,7 @@ async fn handle_request(mut req: Request) -> Result { info!("request body: {:?}", body); Ok(Response::Ok() - .header("x-head", HeaderValue::from_static("dummy value!")) + .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .body(body)) } diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index d6477b152..a84e9aac6 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -19,7 +19,10 @@ async fn main() -> io::Result<()> { .finish(|_req| { info!("{:?}", _req); let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); + res.insert_header(( + "x-head", + HeaderValue::from_static("dummy value!"), + )); future::ok::<_, ()>(res.body("Hello world!")) }) .tcp() diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index c86c697a2..fad902d04 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -1,8 +1,7 @@ use std::time::Duration; -// These values are taken from hyper/src/proto/h2/client.rs -const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2mb -const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb +const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB +const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB /// Connector configuration #[derive(Clone)] @@ -19,7 +18,7 @@ pub(crate) struct ConnectorConfig { impl Default for ConnectorConfig { fn default() -> Self { Self { - timeout: Duration::from_secs(1), + timeout: Duration::from_secs(5), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Some(Duration::from_millis(3000)), diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index d22f2c7ac..26d392120 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -5,7 +5,8 @@ use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::Bytes; -use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{err, Either, FutureExt, Ready}; use h2::client::SendRequest; use pin_project::pin_project; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 326a2fc60..425ee0f70 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -100,9 +100,9 @@ impl Connector<(), ()> { fn build_ssl(protocols: Vec>) -> SslConnector { let mut config = ClientConfig::new(); config.set_protocols(&protocols); - config - .root_store - .add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); + config.root_store.add_server_trust_anchors( + &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, + ); SslConnector::Rustls(Arc::new(config)) } @@ -392,11 +392,11 @@ mod connect_impl { Ready, ConnectError>>, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::Right(err(ConnectError::SslIsNotSupported)) @@ -460,11 +460,11 @@ mod connect_impl { InnerConnectorResponseB, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index a5f1b2e8e..8d609f546 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,6 +1,5 @@ use std::io; -use actix_tls::connect::resolver::ResolveError; use derive_more::{Display, From}; #[cfg(feature = "openssl")] @@ -23,7 +22,7 @@ pub enum ConnectError { /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] - Resolver(ResolveError), + Resolver(Box), /// No dns records #[display(fmt = "No dns records found for the input")] diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 758ad8424..24f4207e8 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -45,7 +45,7 @@ where Some(port) => write!(wrt, "{}:{}", host, port), }; - match wrt.get_mut().split().freeze().try_into() { + match wrt.get_mut().split().freeze().try_into_value() { Ok(value) => match head { RequestHeadType::Owned(ref mut head) => { head.headers.insert(HOST, value) diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 4c609ef22..76915f214 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -171,7 +171,7 @@ async fn send_body( } } -// release SendRequest object +/// release SendRequest object fn release( io: SendRequest, pool: Option>, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 7da2b6234..1eebef53b 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -10,10 +10,11 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use actix_utils::task::LocalWaker; +use ahash::AHashMap; use bytes::Bytes; use futures_channel::oneshot; -use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; -use fxhash::FxHashMap; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{poll_fn, FutureExt}; use h2::client::{Connection, SendRequest}; use http::uri::Authority; use indexmap::IndexSet; @@ -45,7 +46,7 @@ impl From for Key { } /// Connections pool -pub(crate) struct ConnectionPool(Rc>, Rc>>); +pub(crate) struct ConnectionPool(Rc, Rc>>); impl ConnectionPool where @@ -53,13 +54,13 @@ where T: Service + 'static, { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { - let connector_rc = Rc::new(RefCell::new(connector)); + let connector_rc = Rc::new(connector); let inner_rc = Rc::new(RefCell::new(Inner { config, acquired: 0, waiters: Slab::new(), waiters_queue: IndexSet::new(), - available: FxHashMap::default(), + available: AHashMap::default(), waker: LocalWaker::new(), })); @@ -98,12 +99,12 @@ where type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.0.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { - let mut connector = self.0.clone(); + fn call(&self, req: Connect) -> Self::Future { + let connector = self.0.clone(); let inner = self.1.clone(); let fut = async move { @@ -257,7 +258,7 @@ struct AvailableConnection { pub(crate) struct Inner { config: ConnectorConfig, acquired: usize, - available: FxHashMap>>, + available: AHashMap>>, waiters: Slab< Option<( Connect, @@ -325,7 +326,7 @@ where { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } } else { @@ -340,7 +341,7 @@ where if let ConnectionType::H1(io) = io { actix_rt::spawn(CloseConnection::new( io, timeout, - )) + )); } } continue; @@ -372,7 +373,7 @@ where self.acquired -= 1; if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } self.check_availability(); @@ -428,7 +429,7 @@ struct ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, { - connector: T, + connector: Rc, inner: Rc>>, } @@ -535,7 +536,7 @@ where rx: Some(rx), inner: Some(inner), config, - }) + }); } } diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 1cd7e4aea..f178db274 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -9,7 +9,7 @@ use bytes::BytesMut; use futures_util::{future, FutureExt}; use time::OffsetDateTime; -// "Sun, 06 Nov 1994 08:49:37 GMT".len() +/// "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; #[derive(Debug, PartialEq, Clone, Copy)] diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index a585962be..9ff154240 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -18,7 +18,6 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -// re-export for convenience use crate::body::Body; pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index b20dfe11d..5fdcefd6d 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,62 +1,119 @@ -use std::any::{Any, TypeId}; -use std::{fmt, mem}; +use std::{ + any::{Any, TypeId}, + fmt, mem, +}; -use fxhash::FxHashMap; +use ahash::AHashMap; -/// A type map of request extensions. +/// A type map for request extensions. +/// +/// All entries into this map must be owned types (or static references). #[derive(Default)] pub struct Extensions { /// Use FxHasher with a std HashMap with for faster /// lookups on the small `TypeId` (u64 equivalent) keys. - map: FxHashMap>, + map: AHashMap>, } impl Extensions { - /// Create an empty `Extensions`. + /// Creates an empty `Extensions`. #[inline] pub fn new() -> Extensions { Extensions { - map: FxHashMap::default(), + map: AHashMap::default(), } } - /// Insert a type into this `Extensions`. + /// Insert an item into the map. /// - /// If a extension of this type already existed, it will - /// be returned. - pub fn insert(&mut self, val: T) { - self.map.insert(TypeId::of::(), Box::new(val)); + /// If an item of this type was already stored, it will be replaced and returned. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// assert_eq!(map.insert(""), None); + /// assert_eq!(map.insert(1u32), None); + /// assert_eq!(map.insert(2u32), Some(1u32)); + /// assert_eq!(*map.get::().unwrap(), 2u32); + /// ``` + pub fn insert(&mut self, val: T) -> Option { + self.map + .insert(TypeId::of::(), Box::new(val)) + .and_then(downcast_owned) } - /// Check if container contains entry + /// Check if map contains an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// assert!(!map.contains::()); + /// + /// assert_eq!(map.insert(1u32), None); + /// assert!(map.contains::()); + /// ``` pub fn contains(&self) -> bool { self.map.contains_key(&TypeId::of::()) } - /// Get a reference to a type previously inserted on this `Extensions`. + /// Get a reference to an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// map.insert(1u32); + /// assert_eq!(map.get::(), Some(&1u32)); + /// ``` pub fn get(&self) -> Option<&T> { self.map .get(&TypeId::of::()) .and_then(|boxed| boxed.downcast_ref()) } - /// Get a mutable reference to a type previously inserted on this `Extensions`. + /// Get a mutable reference to an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// map.insert(1u32); + /// assert_eq!(map.get_mut::(), Some(&mut 1u32)); + /// ``` pub fn get_mut(&mut self) -> Option<&mut T> { self.map .get_mut(&TypeId::of::()) .and_then(|boxed| boxed.downcast_mut()) } - /// Remove a type from this `Extensions`. + /// Remove an item from the map of a given type. /// - /// If a extension of this type existed, it will be returned. + /// If an item of this type was already stored, it will be returned. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// + /// map.insert(1u32); + /// assert_eq!(map.get::(), Some(&1u32)); + /// + /// assert_eq!(map.remove::(), Some(1u32)); + /// assert!(!map.contains::()); + /// ``` pub fn remove(&mut self) -> Option { - self.map - .remove(&TypeId::of::()) - .and_then(|boxed| boxed.downcast().ok().map(|boxed| *boxed)) + self.map.remove(&TypeId::of::()).and_then(downcast_owned) } /// Clear the `Extensions` of all inserted extensions. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// + /// map.insert(1u32); + /// assert!(map.contains::()); + /// + /// map.clear(); + /// assert!(!map.contains::()); + /// ``` #[inline] pub fn clear(&mut self) { self.map.clear(); @@ -79,6 +136,10 @@ impl fmt::Debug for Extensions { } } +fn downcast_owned(boxed: Box) -> Option { + boxed.downcast().ok().map(|boxed| *boxed) +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 85379b084..9da958563 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -14,7 +14,7 @@ use crate::header::HeaderMap; use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; -const MAX_BUFFER_SIZE: usize = 131_072; +pub(crate) const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; /// Incoming message decoder @@ -203,7 +203,15 @@ impl MessageType for Request { (len, method, uri, version, req.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + // Return None to notify more read are needed for parsing request + Ok(None) + }; + } } }; @@ -222,9 +230,6 @@ impl MessageType for Request { PayloadLength::None => { if method == Method::CONNECT { PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { PayloadType::None } @@ -273,7 +278,14 @@ impl MessageType for ResponseHead { (len, version, status, res.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + Ok(None) + } + } } }; @@ -289,9 +301,6 @@ impl MessageType for ResponseHead { } else if status == StatusCode::SWITCHING_PROTOCOLS { // switching protocol or connect PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { // for HTTP/1.0 read to eof and close connection if msg.version == Version::HTTP_10 { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index feea7f34a..e2ab0a347 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::VecDeque, fmt, future::Future, @@ -46,7 +45,7 @@ bitflags! { } } -#[pin_project::pin_project] +#[pin_project] /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where @@ -91,7 +90,7 @@ where U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { - flow: Rc>>, + flow: Rc>, on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, @@ -140,27 +139,14 @@ where fn is_empty(&self) -> bool { matches!(self, State::None) } - - fn is_call(&self) -> bool { - matches!(self, State::ServiceCall(_)) - } } + enum PollResponse { Upgrade(Request), DoNothing, DrainWriteBuf, } -impl PartialEq for PollResponse { - fn eq(&self, other: &PollResponse) -> bool { - match self { - PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf), - PollResponse::DoNothing => matches!(other, PollResponse::DoNothing), - _ => false, - } - } -} - impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, @@ -177,7 +163,7 @@ where pub(crate) fn new( stream: T, config: ServiceConfig, - services: Rc>>, + flow: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -187,7 +173,7 @@ where config, BytesMut::with_capacity(HW_BUFFER_SIZE), None, - services, + flow, on_connect_data, peer_addr, ) @@ -200,7 +186,7 @@ where config: ServiceConfig, read_buf: BytesMut, timeout: Option, - services: Rc>>, + flow: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -230,7 +216,7 @@ where io: Some(io), codec, read_buf, - flow: services, + flow, on_connect_data, flags, peer_addr, @@ -270,13 +256,14 @@ where } // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(self: Pin<&mut Self>) { + fn client_disconnected(self: Pin<&mut Self>, err: impl Into) { let this = self.project(); this.flags .insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); } + *this.error = Some(err.into()); } /// Flush stream @@ -325,9 +312,10 @@ where message: Response<()>, body: ResponseBody, ) -> Result<(), DispatchError> { + let size = body.size(); let mut this = self.project(); this.codec - .encode(Message::Item((message, body.size())), &mut this.write_buf) + .encode(Message::Item((message, size)), &mut this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -336,74 +324,76 @@ where })?; this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); - match body.size() { + match size { BodySize::None | BodySize::Empty => this.state.set(State::None), _ => this.state.set(State::SendPayload(body)), }; Ok(()) } - fn send_continue(self: Pin<&mut Self>) { - self.project() - .write_buf - .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); - } - fn poll_response( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { loop { let mut this = self.as_mut().project(); - // state is not changed on Poll::Pending. - // other variant and conditions always trigger a state change(or an error). - let state_change = match this.state.project() { + match this.state.as_mut().project() { + // no future is in InnerDispatcher state. pop next message. StateProj::None => match this.messages.pop_front() { + // handle request message. Some(DispatcherMessage::Item(req)) => { - self.as_mut().handle_request(req, cx)?; - true + // Handle `EXPECT: 100-Continue` header + if req.head().expect() { + // set InnerDispatcher state and continue loop to poll it. + let task = this.flow.expect.call(req); + this.state.set(State::ExpectCall(task)); + } else { + // the same as expect call. + let task = this.flow.service.call(req); + this.state.set(State::ServiceCall(task)); + }; } + // handle error message. Some(DispatcherMessage::Error(res)) => { + // send_response would update InnerDispatcher state to SendPayload or + // None(If response body is empty). + // continue loop to poll it. self.as_mut() .send_response(res, ResponseBody::Other(Body::Empty))?; - true } + // return with upgrade request and poll it exclusively. Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } - None => false, - }, - StateProj::ExpectCall(fut) => match fut.poll(cx) { - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - this = self.as_mut().project(); - let fut = this.flow.borrow_mut().service.call(req); - this.state.set(State::ServiceCall(fut)); - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_response(res, body.into_body())?; - true - } - Poll::Pending => false, + // all messages are dealt with. + None => return Ok(PollResponse::DoNothing), }, StateProj::ServiceCall(fut) => match fut.poll(cx) { + // service call resolved. send response. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; - continue; } + // send service call error as response Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; - true } - Poll::Pending => false, + // service call pending and could be waiting for more chunk messages. + // (pipeline message limit and/or payload can_read limit) + Poll::Pending => { + // no new message is decoded and no new payload is feed. + // nothing to do except waiting for new incoming data from client. + if !self.as_mut().poll_request(cx)? { + return Ok(PollResponse::DoNothing); + } + // otherwise keep loop. + } }, StateProj::SendPayload(mut stream) => { + // keep populate writer buffer until buffer size limit hit, + // get blocked or finished. loop { if this.write_buf.len() < HW_BUFFER_SIZE { match stream.as_mut().poll_next(cx) { @@ -412,50 +402,60 @@ where Message::Chunk(Some(item)), &mut this.write_buf, )?; - continue; } Poll::Ready(None) => { this.codec.encode( Message::Chunk(None), &mut this.write_buf, )?; - this = self.as_mut().project(); - this.state.set(State::None); + // payload stream finished. + // break and goes out of scope of borrowed stream. + break; } - Poll::Ready(Some(Err(_))) => { - return Err(DispatchError::Unknown) + Poll::Ready(Some(Err(e))) => { + return Err(DispatchError::Service(e)) } + // Payload Stream Pending should only be given when the caller + // promise to wake it up properly. + // + // TODO: Think if it's an good idea to mix in a self wake up. + // It would turn dispatcher into a busy polling style of stream + // handling. (Or another timer as source of scheduled wake up) + // As There is no way to know when or how the caller would wake + // up the stream so a self wake up is an overhead that would + // result in a double polling(or an extra timer) Poll::Pending => return Ok(PollResponse::DoNothing), } } else { + // buffer is beyond max size. + // return and write the whole buffer to io stream. return Ok(PollResponse::DrainWriteBuf); } - break; } - continue; + // break from Poll::Ready(None) on stream finished. + // this is for re borrow InnerDispatcher state and set it to None. + this.state.set(State::None); } - }; - - // state is changed and continue when the state is not Empty - if state_change { - if !self.state.is_empty() { - continue; - } - } else { - // if read-backpressure is enabled and we consumed some data. - // we may read more data and retry - if self.state.is_call() { - if self.as_mut().poll_request(cx)? { - continue; + StateProj::ExpectCall(fut) => match fut.poll(cx) { + // expect resolved. write continue to buffer and set InnerDispatcher state + // to service call. + Poll::Ready(Ok(req)) => { + this.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall(fut)); } - } else if !self.messages.is_empty() { - continue; - } + // send expect error as response + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_response(res, body.into_body())?; + } + // expect must be solved before progress can be made. + Poll::Pending => return Ok(PollResponse::DoNothing), + }, } - break; } - - Ok(PollResponse::DoNothing) } fn handle_request( @@ -463,52 +463,28 @@ where req: Request, cx: &mut Context<'_>, ) -> Result<(), DispatchError> { + let mut this = self.as_mut().project(); + // Handle `EXPECT: 100-Continue` header if req.head().expect() { - // set dispatcher state so the future is pinned. - let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().expect.call(req); + // set InnerDispatcher state so the future is pinned. + let task = this.flow.expect.call(req); this.state.set(State::ExpectCall(task)); } else { // the same as above. - let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); + let task = this.flow.service.call(req); this.state.set(State::ServiceCall(task)); }; // eagerly poll the future for once(or twice if expect is resolved immediately). loop { - match self.as_mut().project().state.project() { - StateProj::ExpectCall(fut) => { - match fut.poll(cx) { - // expect is resolved. continue loop and poll the service call branch. - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); - this.state.set(State::ServiceCall(task)); - continue; - } - // future is pending. return Ok(()) to notify that a new state is - // set and the outer loop should be continue. - Poll::Pending => return Ok(()), - // future is error. send response and return a result. On success - // to notify the dispatcher a new state is set and the outer loop - // should be continue. - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); - let (res, body) = res.replace_body(()); - return self.send_response(res, body.into_body()); - } - } - } + match this.state.as_mut().project() { StateProj::ServiceCall(fut) => { // return no matter the service call future's result. return match fut.poll(cx) { // future is resolved. send response and return a result. On success - // to notify the dispatcher a new state is set and the outer loop - // should be continue. + // to notify the dispatcher a new InnerDispatcher state is set and the + // outer loop should be continue. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) @@ -523,6 +499,28 @@ where } }; } + StateProj::ExpectCall(fut) => { + match fut.poll(cx) { + // expect is resolved. continue loop and poll the service call branch. + Poll::Ready(Ok(req)) => { + this.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); + let task = this.flow.service.call(req); + this.state.as_mut().set(State::ServiceCall(task)); + } + // future is pending. return Ok(()) to notify that a new InnerDispatcher + // state is set and the outer loop should be continue. + Poll::Pending => return Ok(()), + // future is error. send response and return a result. On success + // to notify the dispatcher a new InnerDispatcher state is set and + // the outer loop should be continue. + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + return self.send_response(res, body.into_body()); + } + } + } _ => unreachable!( "State must be set to ServiceCall or ExceptCall in handle_request" ), @@ -549,28 +547,39 @@ where this.flags.insert(Flags::STARTED); match msg { + // handle new request. Message::Item(mut req) => { - let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream - && this.flow.borrow().upgrade.is_some() - { - this.messages.push_back(DispatcherMessage::Upgrade(req)); - break; - } - if pl == MessageType::Payload || pl == MessageType::Stream { - let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); - req = req1; - *this.payload = Some(ps); + match this.codec.message_type() { + // break when upgrade received. + // existing buffer and io stream would be handled by framed + // after upgrade success. + MessageType::Stream if this.flow.upgrade.is_some() => { + this.messages + .push_back(DispatcherMessage::Upgrade(req)); + break; + } + // construct request and payload. + MessageType::Payload | MessageType::Stream => { + // PayloadSender and Payload are smart pointers share the same + // state. Payload is pass to Request and handed to service + // for extracting state. PayloadSender is held by dispatcher + // to push new data/error to state. + let (ps, pl) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; + *this.payload = Some(ps); + } + // Ignore empty payload. + MessageType::None => {} } - // handle request early + // handle request early if no future lives in InnerDispatcher state. if this.state.is_empty() { self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); @@ -578,54 +587,60 @@ where this.messages.push_back(DispatcherMessage::Item(req)); } } - Message::Chunk(Some(chunk)) => { - if let Some(ref mut payload) = this.payload { - payload.feed_data(chunk); - } else { + Message::Chunk(Some(chunk)) => match this.payload { + Some(ref mut payload) => payload.feed_data(chunk), + None => { error!( "Internal server error: unexpected payload chunk" ); - this.flags.insert(Flags::READ_DISCONNECT); - this.messages.push_back(DispatcherMessage::Error( + self.as_mut().response_error( Response::InternalServerError().finish().drop_body(), - )); - *this.error = Some(DispatchError::InternalError); + DispatchError::InternalError, + ); + this = self.project(); break; } - } - Message::Chunk(None) => { - if let Some(mut payload) = this.payload.take() { - payload.feed_eof(); - } else { + }, + Message::Chunk(None) => match this.payload.take() { + Some(mut payload) => payload.feed_eof(), + None => { error!("Internal server error: unexpected eof"); - this.flags.insert(Flags::READ_DISCONNECT); - this.messages.push_back(DispatcherMessage::Error( + self.as_mut().response_error( Response::InternalServerError().finish().drop_body(), - )); - *this.error = Some(DispatchError::InternalError); + DispatchError::InternalError, + ); + this = self.project(); break; } - } + }, } } Ok(None) => break, Err(ParseError::Io(e)) => { - self.as_mut().client_disconnected(); + self.as_mut().client_disconnected(e); this = self.as_mut().project(); - *this.error = Some(DispatchError::Io(e)); + break; + } + // big size requests overflow should be responded with 413 + Err(ParseError::TooLarge) => { + if let Some(mut payload) = this.payload.take() { + payload.set_error(PayloadError::EncodingCorrupted); + } + self.as_mut().response_error( + Response::PayloadTooLarge().finish().drop_body(), + ParseError::TooLarge, + ); + this = self.project(); break; } Err(e) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); } - // Malformed requests should be responded with 400 - this.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish().drop_body(), - )); - this.flags.insert(Flags::READ_DISCONNECT); - *this.error = Some(e.into()); + self.as_mut() + .response_error(Response::BadRequest().finish().drop_body(), e); + this = self.project(); break; } } @@ -636,6 +651,7 @@ where *this.ka_expire = expire; } } + Ok(updated) } @@ -645,85 +661,172 @@ where cx: &mut Context<'_>, ) -> Result<(), DispatchError> { let mut this = self.as_mut().project(); - if this.ka_timer.is_none() { - // shutdown timeout - if this.flags.contains(Flags::SHUTDOWN) { - if let Some(interval) = this.codec.config().client_disconnect_timer() { - this.ka_timer.set(Some(sleep_until(interval))); - } else { - this.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = this.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - return Ok(()); - } - } else { - return Ok(()); - } - } - match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) { - Poll::Ready(()) => { - // if we get timeout during shutdown, drop connection + // when a branch is not explicit return early it's meant to fall through + // and return as Ok(()) + match this.ka_timer.as_mut().as_pin_mut() { + None => { + // conditionally go into shutdown timeout if this.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline() - >= *this.ka_expire - { - // check for any outstanding tasks - if this.state.is_empty() && this.write_buf.is_empty() { - if this.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - this.flags.insert(Flags::SHUTDOWN); + if let Some(deadline) = this.codec.config().client_disconnect_timer() + { + // write client disconnect time out and poll again to + // go into Some> branch + this.ka_timer.set(Some(sleep_until(deadline))); + return self.poll_keepalive(cx); + } else { + this.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = this.payload.take() { + payload.set_error(PayloadError::Incomplete(None)); + } + } + } + } + Some(mut timer) => { + // only operate when keep-alive timer is resolved. + if timer.as_mut().poll(cx).is_ready() { + // got timeout during shutdown, drop connection + if this.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + // exceed deadline. check for any outstanding tasks + } else if timer.deadline() >= *this.ka_expire { + // have no task at hand. + if this.state.is_empty() && this.write_buf.is_empty() { + if this.flags.contains(Flags::STARTED) { + trace!("Keep-alive timeout, close connection"); + this.flags.insert(Flags::SHUTDOWN); - // start shutdown timer - if let Some(deadline) = - this.codec.config().client_disconnect_timer() - { - if let Some(mut timer) = - this.ka_timer.as_mut().as_pin_mut() + // start shutdown timeout + if let Some(deadline) = + this.codec.config().client_disconnect_timer() { timer.as_mut().reset(deadline); let _ = timer.poll(cx); + } else { + // no shutdown timeout, drop socket + this.flags.insert(Flags::WRITE_DISCONNECT); } } else { - // no shutdown timeout, drop socket - this.flags.insert(Flags::WRITE_DISCONNECT); - return Ok(()); + // timeout on first request (slow request) return 408 + if !this.flags.contains(Flags::STARTED) { + trace!("Slow request timeout"); + let _ = self.as_mut().send_response( + Response::RequestTimeout().finish().drop_body(), + ResponseBody::Other(Body::Empty), + ); + this = self.project(); + } else { + trace!("Keep-alive connection timeout"); + } + this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); + this.state.set(State::None); } - } else { - // timeout on first request (slow request) return 408 - if !this.flags.contains(Flags::STARTED) { - trace!("Slow request timeout"); - let _ = self.as_mut().send_response( - Response::RequestTimeout().finish().drop_body(), - ResponseBody::Other(Body::Empty), - ); - this = self.as_mut().project(); - } else { - trace!("Keep-alive connection timeout"); - } - this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - this.state.set(State::None); - } - } else if let Some(deadline) = - this.codec.config().keep_alive_expire() - { - if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { + // still have unfinished task. try to reset and register keep-alive. + } else if let Some(deadline) = + this.codec.config().keep_alive_expire() + { timer.as_mut().reset(deadline); let _ = timer.poll(cx); } + // timer resolved but still have not met the keep-alive expire deadline. + // reset and register for later wakeup. + } else { + timer.as_mut().reset(*this.ka_expire); + let _ = timer.poll(cx); } - } else if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { - timer.as_mut().reset(*this.ka_expire); - let _ = timer.poll(cx); } } - Poll::Pending => {} } - Ok(()) } + + /// Returns true when io stream can be disconnected after write to it. + /// + /// It covers these conditions: + /// + /// - `Flags::READ_DISCONNECT` flag active. + /// - `std::io::ErrorKind::ConnectionReset` after partial read. + /// - all data read done. + #[inline(always)] + fn read_available( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result { + let this = self.project(); + + if this.flags.contains(Flags::READ_DISCONNECT) { + return Ok(true); + }; + + let mut io = Pin::new(this.io.as_mut().unwrap()); + + let mut read_some = false; + + loop { + // grow buffer if necessary. + let remaining = this.read_buf.capacity() - this.read_buf.len(); + if remaining < LW_BUFFER_SIZE { + this.read_buf.reserve(HW_BUFFER_SIZE - remaining); + } + + match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { + Poll::Pending => return Ok(false), + Poll::Ready(Ok(n)) => { + if n == 0 { + return Ok(true); + } else { + // Return early when read buf exceed decoder's max buffer size. + if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { + // at this point it's not known io is still scheduled to + // be waked up. so force wake up dispatcher just in case. + // TODO: figure out the overhead. + cx.waker().wake_by_ref(); + return Ok(false); + } + + read_some = true; + } + } + Poll::Ready(Err(err)) => { + return if err.kind() == io::ErrorKind::WouldBlock { + Ok(false) + } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { + Ok(true) + } else { + Err(DispatchError::Io(err)) + } + } + } + } + } + + /// call upgrade service with request. + fn upgrade(self: Pin<&mut Self>, req: Request) -> U::Future { + let this = self.project(); + let mut parts = FramedParts::with_read_buf( + this.io.take().unwrap(), + mem::take(this.codec), + mem::take(this.read_buf), + ); + parts.write_buf = mem::take(this.write_buf); + let framed = Framed::from_parts(parts); + this.flow.upgrade.as_ref().unwrap().call((req, framed)) + } + + /// response error handler. + fn response_error( + self: Pin<&mut Self>, + res: Response<()>, + err: impl Into, + ) { + let this = self.project(); + // set flag to read disconnect so no new data is read. + this.flags.insert(Flags::READ_DISCONNECT); + // add response to message and send back to client. + this.messages.push_back(DispatcherMessage::Error(res)); + // attach error for resolve dispatcher future with error. + *this.error = Some(err.into()); + } } impl Future for Dispatcher @@ -757,9 +860,10 @@ where if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer + // flush buffer. inner.as_mut().poll_flush(cx)?; if !inner.write_buf.is_empty() { + // still have unfinished data. wait. Poll::Pending } else { Pin::new(inner.project().io.as_mut().unwrap()) @@ -768,61 +872,46 @@ where } } } else { - // read socket into a buf - let should_disconnect = - if !inner.flags.contains(Flags::READ_DISCONNECT) { - let mut inner_p = inner.as_mut().project(); - read_available( - cx, - inner_p.io.as_mut().unwrap(), - &mut inner_p.read_buf, - )? - } else { - None - }; + // read from io stream and fill read buffer. + let should_disconnect = inner.as_mut().read_available(cx)?; inner.as_mut().poll_request(cx)?; - if let Some(true) = should_disconnect { - let inner_p = inner.as_mut().project(); - inner_p.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = inner_p.payload.take() { + + // io stream should to be closed. + if should_disconnect { + let inner = inner.as_mut().project(); + inner.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = inner.payload.take() { payload.feed_eof(); } }; loop { - let inner_p = inner.as_mut().project(); - let remaining = - inner_p.write_buf.capacity() - inner_p.write_buf.len(); - if remaining < LW_BUFFER_SIZE { - inner_p.write_buf.reserve(HW_BUFFER_SIZE - remaining); + // grow buffer if necessary. + { + let inner = inner.as_mut().project(); + let remaining = + inner.write_buf.capacity() - inner.write_buf.len(); + if remaining < LW_BUFFER_SIZE { + inner.write_buf.reserve(HW_BUFFER_SIZE - remaining); + } } - let result = inner.as_mut().poll_response(cx)?; - let drain = result == PollResponse::DrainWriteBuf; - // switch to upgrade handler - if let PollResponse::Upgrade(req) = result { - let inner_p = inner.as_mut().project(); - let mut parts = FramedParts::with_read_buf( - inner_p.io.take().unwrap(), - mem::take(inner_p.codec), - mem::take(inner_p.read_buf), - ); - parts.write_buf = mem::take(inner_p.write_buf); - let framed = Framed::from_parts(parts); - let upgrade = inner_p - .flow - .borrow_mut() - .upgrade - .take() - .unwrap() - .call((req, framed)); - self.as_mut() - .project() - .inner - .set(DispatcherState::Upgrade(upgrade)); - return self.poll(cx); - } + // poll_response and populate write buffer. + // drain indicate if write buffer should be emptied before next run. + let drain = match inner.as_mut().poll_response(cx)? { + PollResponse::DrainWriteBuf => true, + PollResponse::DoNothing => false, + // upgrade request and goes Upgrade variant of DispatcherState. + PollResponse::Upgrade(req) => { + let upgrade = inner.upgrade(req); + self.as_mut() + .project() + .inner + .set(DispatcherState::Upgrade(upgrade)); + return self.poll(cx); + } + }; // we didn't get WouldBlock from write operation, // so data get written to kernel completely (macOS) @@ -840,28 +929,29 @@ where return Poll::Ready(Ok(())); } + // check if still have unsolved future in InnerDispatcher state. let is_empty = inner.state.is_empty(); - let inner_p = inner.as_mut().project(); + let inner = inner.as_mut().project(); // read half is closed and we do not processing any responses - if inner_p.flags.contains(Flags::READ_DISCONNECT) && is_empty { - inner_p.flags.insert(Flags::SHUTDOWN); + if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty { + inner.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors - if is_empty && inner_p.write_buf.is_empty() { - if let Some(err) = inner_p.error.take() { + if is_empty && inner.write_buf.is_empty() { + if let Some(err) = inner.error.take() { Poll::Ready(Err(err)) } // disconnect if keep-alive is not enabled - else if inner_p.flags.contains(Flags::STARTED) - && !inner_p.flags.intersects(Flags::KEEPALIVE) + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) { - inner_p.flags.insert(Flags::SHUTDOWN); + inner.flags.insert(Flags::SHUTDOWN); self.poll(cx) } // disconnect if shutdown - else if inner_p.flags.contains(Flags::SHUTDOWN) { + else if inner.flags.contains(Flags::SHUTDOWN) { self.poll(cx) } else { Poll::Pending @@ -879,61 +969,6 @@ where } } -/// Returns either: -/// - `Ok(Some(true))` - data was read and done reading all data. -/// - `Ok(Some(false))` - data was read but there should be more to read. -/// - `Ok(None)` - no data was read but there should be more to read later. -/// - Unhandled Errors -fn read_available( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Result, io::Error> -where - T: AsyncRead + Unpin, -{ - let mut read_some = false; - - loop { - // If buf is full return but do not disconnect since - // there is more reading to be done - if buf.len() >= HW_BUFFER_SIZE { - return Ok(Some(false)); - } - - let remaining = buf.capacity() - buf.len(); - if remaining < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE - remaining); - } - - match actix_codec::poll_read_buf(Pin::new(io), cx, buf) { - Poll::Pending => { - return if read_some { Ok(Some(false)) } else { Ok(None) }; - } - Poll::Ready(Ok(n)) => { - if n == 0 { - return Ok(Some(true)); - } else { - read_some = true; - } - } - Poll::Ready(Err(err)) => { - return if err.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Some(false)) - } else { - Ok(None) - } - } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(Some(true)) - } else { - Err(err) - } - } - } - } -} - #[cfg(test)] mod tests { use std::str; diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index c3e4ccdaa..65856edf6 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; @@ -26,11 +26,9 @@ impl Service for ExpectHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { ready(Ok(req)) // TODO: add some way to trigger error // Err(error::ErrorExpectationFailed("test")) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index aed700eed..b79453ebd 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -367,7 +366,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, on_connect_ext: Option>>, cfg: ServiceConfig, _phantom: PhantomData, @@ -417,9 +416,9 @@ where type Error = DispatchError; type Future = Dispatcher; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -429,7 +428,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -440,7 +440,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -460,7 +460,7 @@ where } } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 007aff1bf..5e24d84e3 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; @@ -28,11 +28,9 @@ impl Service<(Request, Framed)> for UpgradeHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _: (Request, Framed)) -> Self::Future { + fn call(&self, _: (Request, Framed)) -> Self::Future { ready(Ok(())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 959c34f13..5ccd2a9d1 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::net; @@ -37,7 +36,7 @@ where S: Service, B: MessageBody, { - flow: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -56,7 +55,7 @@ where B: MessageBody, { pub(crate) fn new( - services: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -80,7 +79,7 @@ where }; Dispatcher { - flow: services, + flow, config, peer_addr, connection, @@ -138,7 +137,7 @@ where let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( - this.flow.borrow_mut().service.call(req), + this.flow.service.call(req), Some(res), ), config: this.config.clone(), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 36f7dc311..36c76b17c 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -249,7 +248,7 @@ pub struct H2ServiceHandler where S: Service, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -290,15 +289,15 @@ where type Error = DispatchError; type Future = H2ServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.flow.service.poll_ready(cx).map_err(|e| { let e = e.into(); error!("Service readiness error: {:?}", e); DispatchError::Service(e) }) } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); @@ -321,7 +320,7 @@ where { Incoming(Dispatcher), Handshake( - Option>>>, + Option>>, Option, Option, OnConnectData, diff --git a/actix-http/src/header/common/accept.rs b/actix-http/src/header/common/accept.rs index da26b0261..775da3394 100644 --- a/actix-http/src/header/common/accept.rs +++ b/actix-http/src/header/common/accept.rs @@ -32,50 +32,36 @@ header! { /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` /// /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// qitem("application/xhtml+xml".parse().unwrap()), @@ -90,7 +76,6 @@ header! { /// ), /// ]) /// ); - /// # } /// ``` (Accept, header::ACCEPT) => (QualityItem)+ @@ -132,7 +117,7 @@ header! { #[test] fn test_fuzzing1() { use crate::test::TestRequest; - let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish(); + let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } diff --git a/actix-http/src/header/common/accept_charset.rs b/actix-http/src/header/common/accept_charset.rs index 291ca53b6..db530a8bc 100644 --- a/actix-http/src/header/common/accept_charset.rs +++ b/actix-http/src/header/common/accept_charset.rs @@ -21,44 +21,37 @@ header! { /// * `iso-8859-5, unicode-1-1;q=0.8` /// /// # Examples - /// ```rust - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); - /// # } /// ``` - /// ```rust - /// # extern crate actix_http; + /// + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Iso_8859_10, q(200)), /// ]) /// ); - /// # } /// ``` - /// ```rust - /// # extern crate actix_http; + /// + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); - /// # } /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ diff --git a/actix-http/src/header/common/accept_language.rs b/actix-http/src/header/common/accept_language.rs index 55879b57f..a7ad00863 100644 --- a/actix-http/src/header/common/accept_language.rs +++ b/actix-http/src/header/common/accept_language.rs @@ -22,41 +22,35 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); - /// builder.set( + /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { + /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), /// QualityItem::new(langtag!(en;;;GB), q(800)), /// QualityItem::new(langtag!(en), q(700)), /// ]) /// ); - /// # } /// ``` (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ diff --git a/actix-http/src/header/common/allow.rs b/actix-http/src/header/common/allow.rs index 88c21763c..06b1efedc 100644 --- a/actix-http/src/header/common/allow.rs +++ b/actix-http/src/header/common/allow.rs @@ -22,38 +22,28 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; + /// use actix_http::http::{header::Allow, Method}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// Allow(vec![Method::GET]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; + /// use actix_http::http::{header::Allow, Method}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// Allow(vec![ /// Method::GET, /// Method::POST, /// Method::PATCH, /// ]) /// ); - /// # } /// ``` (Allow, header::ALLOW) => (Method)* diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index ec94ce4a9..94ce9a750 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -28,12 +28,12 @@ use crate::header::{ /// * `max-age=30` /// /// # Examples -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); +/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ```rust @@ -41,7 +41,7 @@ use crate::header::{ /// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![ +/// builder.insert_header(CacheControl(vec![ /// CacheDirective::NoCache, /// CacheDirective::Private, /// CacheDirective::MaxAge(360u32), @@ -82,7 +82,7 @@ impl fmt::Display for CacheControl { impl IntoHeaderValue for CacheControl { type Error = header::InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); header::HeaderValue::from_maybe_shared(writer.take()) @@ -196,7 +196,8 @@ mod tests { #[test] fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "no-cache, private")) .finish(); let cache = Header::parse(&req); assert_eq!( @@ -210,9 +211,9 @@ mod tests { #[test] fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "max-age=100, private")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -225,8 +226,9 @@ mod tests { #[test] fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "max-age=\"200\"")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -236,8 +238,9 @@ mod tests { #[test] fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "foo, bar=baz")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -250,7 +253,9 @@ mod tests { #[test] fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "foo=")) + .finish(); let cache: Result = Header::parse(&req); assert_eq!(cache.ok(), None) } diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 4c512acbe..ecc59aba3 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -1,10 +1,10 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml +//! # References +//! +//! "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt +//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt +//! "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt +//! Browser conformance tests at: http://greenbytes.de/tech/tc2231/ +//! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml use lazy_static::lazy_static; use regex::Regex; @@ -454,7 +454,7 @@ impl ContentDisposition { impl IntoHeaderValue for ContentDisposition { type Error = header::InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); header::HeaderValue::from_maybe_shared(writer.take()) diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/common/content_encoding.rs new file mode 100644 index 000000000..b93d66101 --- /dev/null +++ b/actix-http/src/header/common/content_encoding.rs @@ -0,0 +1,106 @@ +use std::{convert::Infallible, str::FromStr}; + +use http::header::InvalidHeaderValue; + +use crate::{ + error::ParseError, + header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue}, + HttpMessage, +}; + +/// Represents a supported content encoding. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation. + Auto, + + /// A format using the Brotli algorithm. + Br, + + /// A format using the zlib structure with deflate algorithm. + Deflate, + + /// Gzip algorithm. + Gzip, + + /// Indicates the identity function (i.e. no compression, nor modification). + Identity, +} + +impl ContentEncoding { + /// Is the content compressed? + #[inline] + pub fn is_compression(self) -> bool { + matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) + } + + /// Convert content encoding to string + #[inline] + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + /// Default Q-factor (quality) value. + #[inline] + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +impl Default for ContentEncoding { + fn default() -> Self { + Self::Identity + } +} + +impl FromStr for ContentEncoding { + type Err = Infallible; + + fn from_str(val: &str) -> Result { + Ok(Self::from(val)) + } +} + +impl From<&str> for ContentEncoding { + fn from(val: &str) -> ContentEncoding { + let val = val.trim(); + + if val.eq_ignore_ascii_case("br") { + ContentEncoding::Br + } else if val.eq_ignore_ascii_case("gzip") { + ContentEncoding::Gzip + } else if val.eq_ignore_ascii_case("deflate") { + ContentEncoding::Deflate + } else { + ContentEncoding::default() + } + } +} + +impl IntoHeaderValue for ContentEncoding { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + Ok(HeaderValue::from_static(self.as_str())) + } +} + +impl Header for ContentEncoding { + fn name() -> HeaderName { + header::CONTENT_ENCODING + } + + fn parse(msg: &T) -> Result { + from_one_raw_str(msg.headers().get(Self::name())) + } +} diff --git a/actix-http/src/header/common/content_language.rs b/actix-http/src/header/common/content_language.rs index 838981a39..e9be67a1b 100644 --- a/actix-http/src/header/common/content_language.rs +++ b/actix-http/src/header/common/content_language.rs @@ -23,38 +23,31 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { + /// use actix_http::http::header::{ContentLanguage, qitem}; + /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(en)), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { + /// use actix_http::http::header::{ContentLanguage, qitem}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(da)), /// qitem(langtag!(en;;;GB)), /// ]) /// ); - /// # } /// ``` (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ diff --git a/actix-http/src/header/common/content_range.rs b/actix-http/src/header/common/content_range.rs index 9a604c641..8b7552377 100644 --- a/actix-http/src/header/common/content_range.rs +++ b/actix-http/src/header/common/content_range.rs @@ -200,7 +200,7 @@ impl Display for ContentRangeSpec { impl IntoHeaderValue for ContentRangeSpec { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); HeaderValue::from_maybe_shared(writer.take()) diff --git a/actix-http/src/header/common/content_type.rs b/actix-http/src/header/common/content_type.rs index a0baa5637..ac5c7e5b8 100644 --- a/actix-http/src/header/common/content_type.rs +++ b/actix-http/src/header/common/content_type.rs @@ -30,31 +30,24 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::ContentType; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentType::json() /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::ContentType; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) + /// builder.insert_header( + /// ContentType(mime::TEXT_HTML) /// ); - /// # } /// ``` (ContentType, CONTENT_TYPE) => [Mime] @@ -99,6 +92,7 @@ impl ContentType { pub fn form_url_encoded() -> ContentType { ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) } + /// A constructor to easily create a `Content-Type: image/jpeg` header. #[inline] pub fn jpeg() -> ContentType { diff --git a/actix-http/src/header/common/date.rs b/actix-http/src/header/common/date.rs index 784100e8d..e5ace95e6 100644 --- a/actix-http/src/header/common/date.rs +++ b/actix-http/src/header/common/date.rs @@ -19,13 +19,15 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::SystemTime; /// use actix_http::Response; /// use actix_http::http::header::Date; - /// use std::time::SystemTime; /// /// let mut builder = Response::Ok(); - /// builder.set(Date(SystemTime::now().into())); + /// builder.insert_header( + /// Date(SystemTime::now().into()) + /// ); /// ``` (Date, DATE) => [HttpDate] diff --git a/actix-http/src/header/common/etag.rs b/actix-http/src/header/common/etag.rs index 325b91cbf..4c1e8d262 100644 --- a/actix-http/src/header/common/etag.rs +++ b/actix-http/src/header/common/etag.rs @@ -27,20 +27,24 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); + /// builder.insert_header( + /// ETag(EntityTag::new(false, "xyzzy".to_owned())) + /// ); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); + /// builder.insert_header( + /// ETag(EntityTag::new(true, "xyzzy".to_owned())) + /// ); /// ``` (ETag, ETAG) => [EntityTag] diff --git a/actix-http/src/header/common/expires.rs b/actix-http/src/header/common/expires.rs index 3b9a7873d..79563955d 100644 --- a/actix-http/src/header/common/expires.rs +++ b/actix-http/src/header/common/expires.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); + /// builder.insert_header( + /// Expires(expiration.into()) + /// ); /// ``` (Expires, EXPIRES) => [HttpDate] diff --git a/actix-http/src/header/common/if_match.rs b/actix-http/src/header/common/if_match.rs index 7e0e9a7e0..db255e91a 100644 --- a/actix-http/src/header/common/if_match.rs +++ b/actix-http/src/header/common/if_match.rs @@ -29,20 +29,20 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::IfMatch; /// /// let mut builder = Response::Ok(); - /// builder.set(IfMatch::Any); + /// builder.insert_header(IfMatch::Any); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{IfMatch, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// IfMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "foobar".to_owned()), diff --git a/actix-http/src/header/common/if_modified_since.rs b/actix-http/src/header/common/if_modified_since.rs index 39aca595d..99c7e441d 100644 --- a/actix-http/src/header/common/if_modified_since.rs +++ b/actix-http/src/header/common/if_modified_since.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); + /// builder.insert_header( + /// IfModifiedSince(modified.into()) + /// ); /// ``` (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] diff --git a/actix-http/src/header/common/if_none_match.rs b/actix-http/src/header/common/if_none_match.rs index 7f6ccb137..464caf1ae 100644 --- a/actix-http/src/header/common/if_none_match.rs +++ b/actix-http/src/header/common/if_none_match.rs @@ -31,20 +31,20 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::IfNoneMatch; /// /// let mut builder = Response::Ok(); - /// builder.set(IfNoneMatch::Any); + /// builder.insert_header(IfNoneMatch::Any); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{IfNoneMatch, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// IfNoneMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "foobar".to_owned()), @@ -73,13 +73,15 @@ mod tests { fn test_if_none_match() { let mut if_none_match: Result; - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); + let req = TestRequest::default() + .insert_header((IF_NONE_MATCH, "*")) + .finish(); if_none_match = Header::parse(&req); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); + let req = TestRequest::default() + .insert_header((IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])) + .finish(); if_none_match = Header::parse(&req); let mut entities: Vec = Vec::new(); diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index b14ad0391..1513b7a44 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -35,31 +35,34 @@ use crate::httpmessage::HttpMessage; /// /// # Examples /// -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{EntityTag, IfRange}; /// /// let mut builder = Response::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); +/// builder.insert_header( +/// IfRange::EntityTag( +/// EntityTag::new(false, "abc".to_owned()) +/// ) +/// ); /// ``` /// -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::IfRange; +/// ``` /// use std::time::{Duration, SystemTime}; +/// use actix_http::{http::header::IfRange, Response}; /// /// let mut builder = Response::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); +/// builder.insert_header( +/// IfRange::Date(fetched.into()) +/// ); /// ``` #[derive(Clone, Debug, PartialEq)] pub enum IfRange { - /// The entity-tag the client has of the resource + /// The entity-tag the client has of the resource. EntityTag(EntityTag), - /// The date when the client retrieved the resource + + /// The date when the client retrieved the resource. Date(HttpDate), } @@ -98,7 +101,7 @@ impl Display for IfRange { impl IntoHeaderValue for IfRange { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); HeaderValue::from_maybe_shared(writer.take()) @@ -110,7 +113,8 @@ mod test_if_range { use super::IfRange as HeaderField; use crate::header::*; use std::str; + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); + test_header!(test2, vec![b"\"abc\""]); test_header!(test3, vec![b"this-is-invalid"], None::); } diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/actix-http/src/header/common/if_unmodified_since.rs index d6c099e64..1c2b4af78 100644 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ b/actix-http/src/header/common/if_unmodified_since.rs @@ -22,14 +22,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); + /// builder.insert_header( + /// IfUnmodifiedSince(modified.into()) + /// ); /// ``` (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] diff --git a/actix-http/src/header/common/last_modified.rs b/actix-http/src/header/common/last_modified.rs index cc888ccb0..65608d846 100644 --- a/actix-http/src/header/common/last_modified.rs +++ b/actix-http/src/header/common/last_modified.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); + /// builder.insert_header( + /// LastModified(modified.into()) + /// ); /// ``` (LastModified, LAST_MODIFIED) => [HttpDate] diff --git a/actix-http/src/header/common/mod.rs b/actix-http/src/header/common/mod.rs index c3d18613c..90e0a855e 100644 --- a/actix-http/src/header/common/mod.rs +++ b/actix-http/src/header/common/mod.rs @@ -18,6 +18,7 @@ pub use self::content_disposition::{ }; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; +pub use self::content_encoding::{ContentEncoding}; pub use self::content_type::ContentType; pub use self::date::Date; pub use self::etag::ETag; @@ -83,7 +84,7 @@ macro_rules! test_header { let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item).take(); + req = req.insert_header((HeaderField::name(), item)).take(); } let req = req.finish(); let value = HeaderField::parse(&req); @@ -110,7 +111,7 @@ macro_rules! test_header { let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req.header(HeaderField::name(), item); + req.insert_header((HeaderField::name(), item)); } let req = req.finish(); let val = HeaderField::parse(&req); @@ -168,7 +169,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -204,7 +205,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -240,8 +241,8 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - self.0.try_into() + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + self.0.try_into_value() } } }; @@ -289,7 +290,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -333,13 +334,14 @@ macro_rules! header { } mod accept_charset; -//mod accept_encoding; +// mod accept_encoding; mod accept; mod accept_language; mod allow; mod cache_control; mod content_disposition; mod content_language; +mod content_encoding; mod content_range; mod content_type; mod date; diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs new file mode 100644 index 000000000..d0d6e7324 --- /dev/null +++ b/actix-http/src/header/into_pair.rs @@ -0,0 +1,117 @@ +use std::convert::TryFrom; + +use http::{ + header::{HeaderName, InvalidHeaderName, InvalidHeaderValue}, + Error as HttpError, HeaderValue, +}; + +use super::{Header, IntoHeaderValue}; + +/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s. +pub trait IntoHeaderPair: Sized { + type Error: Into; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>; +} + +#[derive(Debug)] +pub enum InvalidHeaderPart { + Name(InvalidHeaderName), + Value(InvalidHeaderValue), +} + +impl From for HttpError { + fn from(part_err: InvalidHeaderPart) -> Self { + match part_err { + InvalidHeaderPart::Name(err) => err.into(), + InvalidHeaderPart::Value(err) => err.into(), + } + } +} + +impl IntoHeaderPair for (HeaderName, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (&HeaderName, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name.clone(), value)) + } +} + +impl IntoHeaderPair for (&[u8], V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (&str, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (String, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + (name.as_str(), value).try_into_header_pair() + } +} + +impl IntoHeaderPair for T { + type Error = ::Error; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + Ok((T::name(), self.try_into_value()?)) + } +} diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs new file mode 100644 index 000000000..4ba58e726 --- /dev/null +++ b/actix-http/src/header/into_value.rs @@ -0,0 +1,131 @@ +use std::convert::TryFrom; + +use bytes::Bytes; +use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue}; +use mime::Mime; + +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a HeaderValue. + fn try_into_value(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + Ok(self) + } +} + +impl IntoHeaderValue for &HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + Ok(self.clone()) + } +} + +impl IntoHeaderValue for &str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + self.parse() + } +} + +impl IntoHeaderValue for &[u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::from_maybe_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self) + } +} + +impl IntoHeaderValue for usize { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for i64 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for u64 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for i32 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for u32 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::from_str(self.as_ref()) + } +} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 6ab3509f7..8f20f3e6f 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,33 +1,36 @@ -use std::collections::hash_map::{self, Entry}; -use std::convert::TryFrom; +use std::{ + collections::hash_map::{self, Entry}, + convert::TryFrom, +}; +use ahash::AHashMap; use either::Either; -use fxhash::FxHashMap; use http::header::{HeaderName, HeaderValue}; +use smallvec::{smallvec, SmallVec}; -/// A set of HTTP headers +/// A multi-map of HTTP headers. /// -/// `HeaderMap` is an multi-map of [`HeaderName`] to values. -#[derive(Debug, Clone)] +/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values. +#[derive(Debug, Clone, Default)] pub struct HeaderMap { - pub(crate) inner: FxHashMap, + pub(crate) inner: AHashMap, } #[derive(Debug, Clone)] pub(crate) enum Value { One(HeaderValue), - Multi(Vec), + Multi(SmallVec<[HeaderValue; 4]>), } impl Value { - fn get(&self) -> &HeaderValue { + fn first(&self) -> &HeaderValue { match self { Value::One(ref val) => val, Value::Multi(ref val) => &val[0], } } - fn get_mut(&mut self) -> &mut HeaderValue { + fn first_mut(&mut self) -> &mut HeaderValue { match self { Value::One(ref mut val) => val, Value::Multi(ref mut val) => &mut val[0], @@ -37,7 +40,7 @@ impl Value { fn append(&mut self, val: HeaderValue) { match self { Value::One(_) => { - let data = std::mem::replace(self, Value::Multi(vec![val])); + let data = std::mem::replace(self, Value::Multi(smallvec![val])); match data { Value::One(val) => self.append(val), Value::Multi(_) => unreachable!(), @@ -55,7 +58,7 @@ impl HeaderMap { /// allocate. pub fn new() -> Self { HeaderMap { - inner: FxHashMap::default(), + inner: AHashMap::default(), } } @@ -69,7 +72,7 @@ impl HeaderMap { /// More capacity than requested may be allocated. pub fn with_capacity(capacity: usize) -> HeaderMap { HeaderMap { - inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()), + inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()), } } @@ -118,7 +121,7 @@ impl HeaderMap { /// is returned. Use `get_all` to get all values associated with a given /// key. Returns `None` if there are no values associated with the key. pub fn get(&self, name: N) -> Option<&HeaderValue> { - self.get2(name).map(|v| v.get()) + self.get2(name).map(|v| v.first()) } fn get2(&self, name: N) -> Option<&Value> { @@ -134,11 +137,11 @@ impl HeaderMap { } } - /// Returns a view of all values associated with a key. + /// Returns an iterator of all values associated with a key. /// - /// The returned view does not incur any allocations and allows iterating - /// the values associated with the key. See [`GetAll`] for more details. - /// Returns `None` if there are no values associated with the key. + /// The returned view does not incur any allocations and allows iterating the values associated + /// with the key. Returns `None` if there are no values associated with the key. Iteration order + /// is not guaranteed to be the same as insertion order. pub fn get_all(&self, name: N) -> GetAll<'_> { GetAll { idx: 0, @@ -153,10 +156,10 @@ impl HeaderMap { /// key. Returns `None` if there are no values associated with the key. pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { match name.as_name() { - Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()), + Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()), Either::Right(s) => { if let Ok(name) = HeaderName::try_from(s) { - self.inner.get_mut(&name).map(|v| v.get_mut()) + self.inner.get_mut(&name).map(|v| v.first_mut()) } else { None } @@ -282,6 +285,7 @@ impl<'a> AsName for &'a String { } } +/// Iterator for all values in a `HeaderMap` with the same name. pub struct GetAll<'a> { idx: usize, item: Option<&'a Value>, @@ -337,7 +341,7 @@ impl<'a> IntoIterator for &'a HeaderMap { pub struct Iter<'a> { idx: usize, - current: Option<(&'a HeaderName, &'a Vec)>, + current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, iter: hash_map::Iter<'a, HeaderName, Value>, } diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 0f87516eb..dc97bf5ff 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,12 +1,9 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) +//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other +//! header utility methods. -use std::convert::TryFrom; -use std::{fmt, str::FromStr}; +use std::fmt; use bytes::{Bytes, BytesMut}; -use http::Error as HttpError; -use mime::Mime; use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -14,22 +11,27 @@ pub use http::header::*; use crate::error::ParseError; use crate::httpmessage::HttpMessage; +mod into_pair; +mod into_value; +mod utils; + mod common; pub(crate) mod map; mod shared; + pub use self::common::*; #[doc(hidden)] pub use self::shared::*; +pub use self::into_pair::IntoHeaderPair; +pub use self::into_value::IntoHeaderValue; #[doc(hidden)] pub use self::map::GetAll; pub use self::map::HeaderMap; +pub use self::utils::*; -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ +/// A trait for any object that already represents a valid header field and value. +pub trait Header: IntoHeaderValue { /// Returns the name of the header field fn name() -> HeaderName; @@ -37,159 +39,6 @@ where fn parse(msg: &T) -> Result; } -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_maybe_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for usize { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for u64 { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(format!("{}", self)) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - Br, - /// A format using the zlib structure with deflate algorithm - Deflate, - /// Gzip algorithm - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - ContentEncoding::Br => "br", - ContentEncoding::Gzip => "gzip", - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - ContentEncoding::Br => 1.1, - ContentEncoding::Gzip => 1.0, - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - let s = s.trim(); - - if s.eq_ignore_ascii_case("br") { - ContentEncoding::Br - } else if s.eq_ignore_ascii_case("gzip") { - ContentEncoding::Gzip - } else if s.eq_ignore_ascii_case("deflate") { - ContentEncoding::Deflate - } else { - ContentEncoding::Identity - } - } -} - #[doc(hidden)] pub(crate) struct Writer { buf: BytesMut, @@ -201,6 +50,7 @@ impl Writer { buf: BytesMut::new(), } } + fn take(&mut self) -> Bytes { self.buf.split().freeze() } @@ -219,164 +69,7 @@ impl fmt::Write for Writer { } } -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited<'a, I: Iterator + 'a, T: FromStr>( - all: I, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value( - val: &str, -) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(crate::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let encoded_value = - percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// -pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { - let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} - -/// Convert http::HeaderMap to a HeaderMap +/// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { fn from(map: http::HeaderMap) -> HeaderMap { let mut new_map = HeaderMap::with_capacity(map.capacity()); @@ -387,8 +80,8 @@ impl From for HeaderMap { } } -// This encode set is used for HTTP header values and is defined at -// https://tools.ietf.org/html/rfc5987#section-3.2 +/// This encode set is used for HTTP header values and is defined at +/// https://tools.ietf.org/html/rfc5987#section-3.2. pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') @@ -410,91 +103,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b']') .add(b'{') .add(b'}'); - -#[cfg(test)] -mod tests { - use super::shared::Charset; - use super::{parse_extended_value, ExtendedValue}; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index 344cfb864..eb383cd6f 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -161,7 +161,7 @@ impl FromStr for EntityTag { impl IntoHeaderValue for EntityTag { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut wrt = Writer::new(); write!(wrt, "{}", self).unwrap(); HeaderValue::from_maybe_shared(wrt.take()) diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs new file mode 100644 index 000000000..6bdcb7922 --- /dev/null +++ b/actix-http/src/header/shared/extended.rs @@ -0,0 +1,193 @@ +use std::{fmt, str::FromStr}; + +use language_tags::LanguageTag; + +use crate::header::{Charset, HTTP_VALUE}; + +// From hyper v0.11.27 src/header/parsing.rs + +/// The value part of an extended parameter consisting of three parts: +/// - The REQUIRED character set name (`charset`). +/// - The OPTIONAL language information (`language_tag`). +/// - A character sequence representing the actual value (`value`), separated by single quotes. +/// +/// It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + + /// The human language details of the `value`, if available. + pub language_tag: Option, + + /// The parameter value, as expressed in octets. + pub value: Vec, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value( + val: &str, +) -> Result { + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3, '\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, + }; + + // Interpret the second piece as a language tag + let language_tag: Option = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(crate::error::ParseError::Header), + }, + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + value, + charset, + language_tag, + }) +} + +impl fmt::Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let encoded_value = + percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!( + vec![163, b' ', b'r', b'a', b't', b'e', b's'], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!( + vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + }; + assert_eq!( + "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value) + ); + } +} diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index d6b9d8001..72a225589 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -48,7 +48,7 @@ impl From for HttpDate { impl IntoHeaderValue for HttpDate { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!( wrt, diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index f2bc91634..72161e46b 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -1,14 +1,16 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; +//! Originally taken from `hyper::header::shared`. mod charset; mod encoding; mod entity; +mod extended; mod httpdate; mod quality_item; + +pub use self::charset::Charset; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; +pub use self::extended::{parse_extended_value, ExtendedValue}; +pub use self::httpdate::HttpDate; +pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use language_tags::LanguageTag; diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs new file mode 100644 index 000000000..e232d462f --- /dev/null +++ b/actix-http/src/header/utils.rs @@ -0,0 +1,63 @@ +use std::{fmt, str::FromStr}; + +use http::HeaderValue; + +use crate::{error::ParseError, header::HTTP_VALUE}; + +/// Reads a comma-delimited raw header into a Vec. +#[inline] +pub fn from_comma_delimited<'a, I, T>(all: I) -> Result, ParseError> +where + I: Iterator + 'a, + T: FromStr, +{ + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +/// Reads a single string when parsing a header. +#[inline] +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +/// Format an array into a comma-delimited string. +#[inline] +pub fn fmt_comma_delimited(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +/// Percent encode a sequence of bytes with a character set defined in +/// +pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { + let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 471fbbcdc..2610b8784 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -13,7 +13,7 @@ use crate::payload::Payload; struct Cookies(Vec>); -/// Trait that implements general purpose operations on http messages +/// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { /// Type of message payload stream type Stream; @@ -30,8 +30,8 @@ pub trait HttpMessage: Sized { /// Mutable reference to a the request's extensions container fn extensions_mut(&self) -> RefMut<'_, Extensions>; + /// Get a header. #[doc(hidden)] - /// Get a header fn get_header(&self) -> Option where Self: Sized, @@ -43,8 +43,8 @@ pub trait HttpMessage: Sized { } } - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. + /// Read the request content type. If request did not contain a *Content-Type* header, an empty + /// string is returned. fn content_type(&self) -> &str { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { @@ -90,7 +90,7 @@ pub trait HttpMessage: Sized { Ok(None) } - /// Check if request has chunked transfer encoding + /// Check if request has chunked transfer encoding. fn chunked(&self) -> Result { if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { if let Ok(s) = encodings.to_str() { @@ -173,11 +173,13 @@ mod tests { #[test] fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "text/plain")) + .finish(); assert_eq!(req.content_type(), "text/plain"); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf=8") - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=utf=8")) + .finish(); assert_eq!(req.content_type(), "application/json"); let req = TestRequest::default().finish(); assert_eq!(req.content_type(), ""); @@ -185,13 +187,15 @@ mod tests { #[test] fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json")) + .finish(); assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); let req = TestRequest::default().finish(); assert_eq!(req.mime_type().unwrap(), None); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf-8") - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=utf-8")) + .finish(); let mt = req.mime_type().unwrap().unwrap(); assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); assert_eq!(mt.type_(), mime::APPLICATION); @@ -200,11 +204,9 @@ mod tests { #[test] fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", - "applicationadfadsfasdflknadsfklnadsfjson", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson")) + .finish(); assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); } @@ -213,27 +215,27 @@ mod tests { let req = TestRequest::default().finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - let req = TestRequest::with_header("content-type", "application/json").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json")) + .finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - let req = TestRequest::with_header( - "content-type", - "application/json; charset=ISO-8859-2", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=ISO-8859-2")) + .finish(); assert_eq!(ISO_8859_2, req.encoding().unwrap()); } #[test] fn test_encoding_error() { - let req = TestRequest::with_header("content-type", "applicatjson").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "applicatjson")) + .finish(); assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - let req = TestRequest::with_header( - "content-type", - "application/json; charset=kkkttktk", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=kkkttktk")) + .finish(); assert_eq!( Some(ContentTypeError::UnknownEncoding), req.encoding().err() @@ -245,15 +247,16 @@ mod tests { let req = TestRequest::default().finish(); assert!(!req.chunked().unwrap()); - let req = - TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + let req = TestRequest::default() + .insert_header((header::TRANSFER_ENCODING, "chunked")) + .finish(); assert!(req.chunked().unwrap()); let req = TestRequest::default() - .header( + .insert_header(( header::TRANSFER_ENCODING, Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ) + )) .finish(); assert!(req.chunked().is_err()); } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index e17b7de0a..cc99130eb 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -53,7 +53,7 @@ pub use self::response::{Response, ResponseBuilder}; pub use self::service::HttpService; pub mod http { - //! Various HTTP related types + //! Various HTTP related types. // re-exports pub use http::header::{HeaderName, HeaderValue}; @@ -64,7 +64,7 @@ pub mod http { pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; - /// Various http headers + /// A collection of HTTP headers and helpers. pub mod header { pub use crate::header::*; } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 0bc84a44e..1031d7dce 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,5 +1,9 @@ -use std::cell::{Ref, RefMut}; -use std::{fmt, net}; +//! HTTP requests. + +use std::{ + cell::{Ref, RefMut}, + fmt, net, +}; use http::{header, Method, Uri, Version}; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 0a1f2cfd2..110514e05 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,10 +1,15 @@ -//! Http response -use std::cell::{Ref, RefMut}; -use std::convert::TryFrom; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, str}; +//! HTTP responses. + +use std::{ + cell::{Ref, RefMut}, + convert::TryInto, + fmt, + future::Future, + ops, + pin::Pin, + str, + task::{Context, Poll}, +}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; @@ -14,7 +19,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; -use crate::header::{Header, IntoHeaderValue}; +use crate::header::{IntoHeaderPair, IntoHeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; @@ -341,93 +346,96 @@ impl ResponseBuilder { self } - /// Set a header. + /// Insert a header, replacing any that were set with an equivalent field name. /// /// ```rust - /// use actix_http::{http, Request, Response, Result}; + /// # use actix_http::Response; + /// use actix_http::http::header::ContentType; /// - /// fn index(req: Request) -> Result { - /// Ok(Response::Ok() - /// .set(http::header::IfModifiedSince( - /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, - /// )) - /// .finish()) - /// } + /// Response::Ok() + /// .insert_header(ContentType(mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.append(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header to existing headers. - /// - /// ```rust - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self + pub fn insert_header(&mut self, header: H) -> &mut Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.insert(key, value), Err(e) => self.err = Some(e.into()), }; } + self } - /// Set a header. + /// Append a header, keeping any that were set with an equivalent field name. /// /// ```rust - /// use actix_http::{http, Request, Response}; + /// # use actix_http::Response; + /// use actix_http::http::header::ContentType; /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .set_header("X-TEST", "value") - /// .set_header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } + /// Response::Ok() + /// .append_header(ContentType(mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Replaced with [`Self::insert_header()`]. + #[deprecated = "Replaced with `insert_header((key, value))`."] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where - HeaderName: TryFrom, - >::Error: Into, + K: TryInto, + K::Error: Into, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; + if self.err.is_some() { + return self; } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.insert_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Replaced with [`Self::append_header()`]. + #[deprecated = "Replaced with `append_header((key, value))`."] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.append_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + self } @@ -458,7 +466,12 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.head, &self.err) { parts.set_connection_type(ConnectionType::Upgrade); } - self.set_header(header::UPGRADE, value) + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self } /// Force close connection, even if it is marked as keep-alive @@ -473,7 +486,7 @@ impl ResponseBuilder { /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. #[inline] pub fn no_chunking(&mut self, len: u64) -> &mut Self { - self.header(header::CONTENT_LENGTH, len); + self.insert_header((header::CONTENT_LENGTH, len)); if let Some(parts) = parts(&mut self.head, &self.err) { parts.no_chunking(true); @@ -488,7 +501,7 @@ impl ResponseBuilder { V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.head, &self.err) { - match value.try_into() { + match value.try_into_value() { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); } @@ -639,27 +652,24 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - #[inline] /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { - self.json2(&value) - } - - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { - match serde_json::to_string(value) { + pub fn json(&mut self, value: T) -> Response + where + T: ops::Deref, + T::Target: Serialize, + { + match serde_json::to_string(&*value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.head, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) } else { true }; + if !contains { - self.header(header::CONTENT_TYPE, "application/json"); + self.insert_header(header::ContentType(mime::APPLICATION_JSON)); } self.body(Body::from(body)) @@ -848,6 +858,8 @@ impl From for Response { #[cfg(test)] mod tests { + use serde_json::json; + use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; @@ -855,8 +867,8 @@ mod tests { #[test] fn test_debug() { let resp = Response::Ok() - .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) - .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); let dbg = format!("{:?}", resp); assert!(dbg.contains("Response")); @@ -867,8 +879,8 @@ mod tests { use crate::httpmessage::HttpMessage; let req = crate::test::TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") + .append_header((COOKIE, "cookie1=value1")) + .append_header((COOKIE, "cookie2=value2")) .finish(); let cookies = req.cookies().unwrap(); @@ -922,7 +934,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok().header("X-TEST", "value").finish(); + let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); assert_eq!(resp.status(), StatusCode::OK); } @@ -963,26 +975,8 @@ mod tests { #[test] fn test_json_ct() { let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2() { - let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2_ct() { - let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json2(&vec!["v1", "v2", "v3"]); + .insert_header((CONTENT_TYPE, "text/json")) + .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); @@ -1081,4 +1075,54 @@ mod tests { let cookie = resp.cookies().next().unwrap(); assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::Ok(); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::Ok(); + res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::Ok(); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::Ok(); + res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); + res.append_header(header::ContentType(mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 357ac4c53..f0121db97 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -440,7 +439,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -454,12 +453,12 @@ pub(super) struct HttpFlow { } impl HttpFlow { - pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc> { - Rc::new(RefCell::new(Self { + pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc { + Rc::new(Self { service, expect, upgrade, - })) + }) } } @@ -509,9 +508,9 @@ where type Error = DispatchError; type Future = HttpServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -521,7 +520,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -532,7 +532,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -553,7 +553,7 @@ where } fn call( - &mut self, + &self, (io, proto, peer_addr): (T, Protocol, Option), ) -> Self::Future { let on_connect_data = @@ -604,7 +604,7 @@ where Option<( Handshake, ServiceConfig, - Rc>>, + Rc>, OnConnectData, Option, )>, diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 3f08bb7ee..8f0a7d21a 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -2,7 +2,6 @@ use std::{ cell::{Ref, RefCell}, - convert::TryFrom, io::{self, Read, Write}, pin::Pin, rc::Rc, @@ -12,14 +11,17 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, Method, Uri, Version}; +use http::{ + header::{self, HeaderValue}, + Method, Uri, Version, +}; -use crate::cookie::{Cookie, CookieJar}; -use crate::header::HeaderMap; -use crate::header::{Header, IntoHeaderValue}; -use crate::payload::Payload; -use crate::Request; +use crate::{ + cookie::{Cookie, CookieJar}, + header::{HeaderMap, IntoHeaderPair}, + payload::Payload, + Request, +}; /// Test `Request` builder /// @@ -36,7 +38,7 @@ use crate::Request; /// } /// } /// -/// let resp = TestRequest::with_header("content-type", "text/plain") +/// let resp = TestRequest::default().insert_header("content-type", "text/plain") /// .run(&index) /// .unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); @@ -69,76 +71,73 @@ impl Default for TestRequest { } impl TestRequest { - /// Create TestRequest and set request uri + /// Create a default TestRequest and then set its URI. pub fn with_uri(path: &str) -> TestRequest { TestRequest::default().uri(path).take() } - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr).take() - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value).take() - } - - /// Set HTTP version of this request + /// Set HTTP version of this request. pub fn version(&mut self, ver: Version) -> &mut Self { parts(&mut self.0).version = ver; self } - /// Set HTTP method of this request + /// Set HTTP method of this request. pub fn method(&mut self, meth: Method) -> &mut Self { parts(&mut self.0).method = meth; self } - /// Set HTTP Uri of this request + /// Set URI of this request. + /// + /// # Panics + /// If provided URI is invalid. pub fn uri(&mut self, path: &str) -> &mut Self { parts(&mut self.0).uri = Uri::from_str(path).unwrap(); self } - /// Set a header - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Ok(value) = hdr.try_into() { - parts(&mut self.0).headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); - } - - /// Set a header - pub fn header(&mut self, key: K, value: V) -> &mut Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(&mut self, header: H) -> &mut Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { - parts(&mut self.0).headers.append(key, value); - return self; + match header.try_into_header_pair() { + Ok((key, value)) => { + parts(&mut self.0).headers.insert(key, value); + } + Err(err) => { + panic!("Error inserting test header: {}.", err.into()); } } - panic!("Can not create header"); + + self } - /// Set cookie for this request + /// Append a header, keeping any that were set with an equivalent field name. + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts(&mut self.0).headers.append(key, value); + } + Err(err) => { + panic!("Error inserting test header: {}.", err.into()); + } + } + + self + } + + /// Set cookie for this request. pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self } - /// Set request payload + /// Set request payload. pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); @@ -150,7 +149,7 @@ impl TestRequest { TestRequest(self.0.take()) } - /// Complete request creation and generate `Request` instance + /// Complete request creation and generate `Request` instance. pub fn finish(&mut self) -> Request { let inner = self.0.take().expect("cannot reuse test request builder"); diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index d37d57eb1..79e015f79 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -3,7 +3,7 @@ use std::ptr::copy_nonoverlapping; use std::slice; -// Holds a slice guaranteed to be shorter than 8 bytes +/// Holds a slice guaranteed to be shorter than 8 bytes. struct ShortSlice<'a> { inner: &'a mut [u8], } @@ -80,8 +80,10 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) } -// Splits a slice into three parts: an unaligned short head and tail, plus an aligned -// u64 mid section. +/// Splits a slice into three parts: +/// - an unaligned short head +/// - an aligned `u64` slice mid section +/// - an unaligned short tail #[inline] fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { let start_ptr = buf.as_ptr() as usize; diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index a2b093ce4..dad2646c1 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -101,7 +101,7 @@ impl ResponseError for HandshakeError { fn error_response(&self) -> Response { match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() - .header(header::ALLOW, "GET") + .insert_header((header::ALLOW, "GET")) .finish(), HandshakeError::NoWebsocketUpgrade => Response::BadRequest() @@ -128,18 +128,12 @@ impl ResponseError for HandshakeError { } /// Verify `WebSocket` handshake request and create handshake response. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } /// Verify `WebSocket` handshake request. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { // WebSocket accepts only GET if req.method != Method::GET { @@ -198,8 +192,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { Response::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) + .insert_header((header::TRANSFER_ENCODING, "chunked")) + .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) .take() } @@ -224,7 +218,7 @@ mod tests { ); let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) + .insert_header((header::UPGRADE, header::HeaderValue::from_static("test"))) .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, @@ -232,10 +226,10 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) + )) .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, @@ -243,14 +237,14 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) + )) .finish(); assert_eq!( HandshakeError::NoVersionHeader, @@ -258,18 +252,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ) + )) .finish(); assert_eq!( HandshakeError::UnsupportedVersion, @@ -277,18 +271,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) + )) .finish(); assert_eq!( HandshakeError::BadWebsocketKey, @@ -296,22 +290,22 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) + )) .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index f78636b9a..91b2412f4 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -38,7 +38,7 @@ async fn test_h1_v2() { let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); - let request = srv.get("/").header("x-test", "111").send(); + let request = srv.get("/").insert_header(("x-test", "111")).send(); let mut response = request.await.unwrap(); assert!(response.status().is_success()); diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index bb4732281..f20cfd70c 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -173,8 +173,8 @@ async fn test_h2_headers() { HttpService::build().h2(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), + builder.insert_header( + (format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -188,7 +188,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } ok::<_, ()>(builder.body(data.clone())) }) @@ -341,7 +341,7 @@ async fn test_h2_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -369,7 +369,7 @@ async fn test_h2_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(header::CONTENT_TYPE, broken_header) + .insert_header((header::CONTENT_TYPE, broken_header)) .body(STR), ) })) @@ -408,7 +408,9 @@ async fn test_h2_service_error() { async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() - .on_connect_ext(|_, data| data.insert(20isize)) + .on_connect_ext(|_, data| { + data.insert(20isize); + }) .h2(|req: Request| { assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index beae359d9..2f6b31f49 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -181,7 +181,7 @@ async fn test_h2_headers() { HttpService::build().h2(move |_| { let mut config = Response::Ok(); for idx in 0..90 { - config.header( + config.insert_header(( format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -196,7 +196,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } future::ok::<_, ()>(config.body(data.clone())) }) @@ -352,7 +352,7 @@ async fn test_h2_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -380,7 +380,7 @@ async fn test_h2_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) + .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) })) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index fa1aeb695..b4ef74406 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -392,7 +392,7 @@ async fn test_h1_headers() { HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { - builder.header( + builder.insert_header(( format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -407,7 +407,7 @@ async fn test_h1_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } future::ok::<_, ()>(builder.body(data.clone())) }).tcp() @@ -561,7 +561,7 @@ async fn test_h1_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -625,7 +625,7 @@ async fn test_h1_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) + .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) })) @@ -662,7 +662,9 @@ async fn test_h1_service_error() { async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() - .on_connect_ext(|_, data| data.insert(20isize)) + .on_connect_ext(|_, data| { + data.insert(20isize); + }) .h1(|req: Request| { assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 976fc9164..7ed9b0df1 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -21,7 +21,7 @@ impl WsService { WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) } - fn set_polled(&mut self) { + fn set_polled(&self) { *self.0.lock().unwrap().1.get_mut() = true; } @@ -44,15 +44,12 @@ where type Error = Error; type Future = Pin>>>; - fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll> { self.set_polled(); Poll::Ready(Ok(())) } - fn call( - &mut self, - (req, mut framed): (Request, Framed), - ) -> Self::Future { + fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { let fut = async move { let res = ws::handshake(req.head()).unwrap().message_body(()); diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 44a7e8d16..8eb63065c 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0f90edb07..05593e701 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.1", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.1" actix-web = { version = "4.0.0-beta.1", default-features = false } @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 2dd93c727..afe17cd21 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -233,14 +233,13 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) - }))) - .await; + let srv = init_service(App::new().service(web::resource("/test").to(|| { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 60942c6c6..10113665b 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -166,11 +166,11 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) + .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) .take(); if let Some(protocol) = protocol { - response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol); + response.insert_header((header::SEC_WEBSOCKET_PROTOCOL, protocol)); } Ok(response) @@ -573,7 +573,7 @@ mod tests { ); let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) + .insert_header((header::UPGRADE, header::HeaderValue::from_static("test"))) .to_http_request(); assert_eq!( HandshakeError::NoWebsocketUpgrade, @@ -581,10 +581,10 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::NoConnectionUpgrade, @@ -592,14 +592,14 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::NoVersionHeader, @@ -607,18 +607,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::UnsupportedVersion, @@ -626,18 +626,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::BadWebsocketKey, @@ -645,22 +645,22 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) + )) .to_http_request(); let resp = handshake(&req).unwrap().finish(); @@ -669,26 +669,26 @@ mod tests { assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING)); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("graphql"), - ) + )) .to_http_request(); let protocols = ["graphql"]; @@ -710,26 +710,26 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("p1, p2, p3"), - ) + )) .to_http_request(); let protocols = vec!["p3", "p2"]; @@ -751,26 +751,26 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("p1,p2,p3"), - ) + )) .to_http_request(); let protocols = vec!["p3", "p2"]; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 00875cf1b..04bd10421 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 50e5be712..ede1e0005 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -175,7 +175,6 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { let vis = &input.vis; let sig = &mut input.sig; let body = &input.block; - let name = &sig.ident; if sig.asyncness.is_none() { return syn::Error::new_spanned(sig.fn_token, "only async fn is supported") @@ -188,7 +187,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { (quote! { #(#attrs)* #vis #sig { - actix_web::rt::System::new(stringify!(#name)) + actix_web::rt::System::new() .block_on(async move { #body }) } }) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 389d09c82..252be1b95 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,5 +1,4 @@ use std::future::Future; -use std::pin::Pin; use std::task::{Context, Poll}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; @@ -8,7 +7,7 @@ use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{ connect, delete, get, head, options, patch, post, put, route, trace, }; -use futures_util::future; +use futures_util::future::{self, LocalBoxFuture}; // Make sure that we can name function as 'config' #[get("/config")] @@ -117,14 +116,13 @@ where { type Response = ServiceResponse; type Error = Error; - #[allow(clippy::type_complexity)] - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); Box::pin(async move { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 89b6121f3..16ec7ad1a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,21 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `ClientRequest::insert_header` method which allows using typed headers. [#1869] +* `ClientRequest::append_header` method which allows using typed headers. [#1869] + +### Changed +* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] + +### Removed +* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] +* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] + +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0dbf80d33..8500e9702 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -40,7 +40,7 @@ compress = ["actix-http/compress"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.3" actix-http = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "2" base64 = "0.13" bytes = "1" @@ -58,17 +58,16 @@ open-ssl = { version = "0.10", package = "openssl", optional = true } rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -# TODO: actix is temporary added as dev dep for actix-macro reason. -# Can be removed when it does not impact tests. -actix = "0.11.0-beta.1" actix-web = { version = "4.0.0-beta.1", features = ["openssl"] } actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.2" actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } + brotli2 = "0.3.2" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } env_logger = "0.7" +rcgen = "0.8" webpki = "0.21" diff --git a/awc/README.md b/awc/README.md index 972a80140..3d18a07c5 100644 --- a/awc/README.md +++ b/awc/README.md @@ -21,7 +21,7 @@ use actix_rt::System; use awc::Client; fn main() { - System::new("test").block_on(async { + System::new().block_on(async { let client = Client::default(); let res = client diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 6be0112d8..39eb24c39 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::convert::TryFrom; use std::fmt; use std::rc::Rc; @@ -24,7 +23,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option>>, + connector: Option>, } impl Default for ClientBuilder { @@ -56,7 +55,7 @@ impl ClientBuilder { ::Future: 'static, T::Future: 'static, { - self.connector = Some(RefCell::new(Box::new(ConnectorWrapper(connector)))); + self.connector = Some(Box::new(ConnectorWrapper(connector))); self } @@ -133,7 +132,7 @@ impl ClientBuilder { V::Error: fmt::Debug, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.headers.append(key, value); } @@ -182,9 +181,7 @@ impl ClientBuilder { if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; - RefCell::new( - Box::new(ConnectorWrapper(connector.finish())) as Box - ) + Box::new(ConnectorWrapper(connector.finish())) as _ }; let config = ClientConfig { headers: self.headers, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 8ee239f76..2ffb8ec37 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -20,14 +20,14 @@ pub(crate) struct ConnectorWrapper(pub T); pub(crate) trait Connect { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, ) -> Pin>>>; fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -36,7 +36,7 @@ pub(crate) trait Connect { /// Send request, returns Response and Framed fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -52,7 +52,7 @@ pub(crate) trait Connect { /// Send request and extra headers, returns Response and Framed fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, @@ -78,7 +78,7 @@ where T::Future: 'static, { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, @@ -101,7 +101,7 @@ where } fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -126,7 +126,7 @@ where } fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -158,7 +158,7 @@ where } fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index f7098863c..878f404c6 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -144,7 +144,7 @@ impl FrozenSendBuilder { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => self.extra_headers.insert(key, value), Err(e) => self.err = Some(e.into()), }, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index aad6ec38b..a92125b5a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -7,7 +7,7 @@ //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); //! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") +//! .insert_header(("User-Agent", "Actix-web")) //! .send() // <- Send http request //! .await?; //! @@ -93,7 +93,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; @@ -134,7 +133,7 @@ use self::connect::{Connect, ConnectorWrapper}; /// let mut client = Client::default(); /// /// let res = client.get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") +/// .insert_header(("User-Agent", "Actix-web")) /// .send() // <- Send http request /// .await; // <- send request and wait for response /// @@ -145,7 +144,7 @@ use self::connect::{Connect, ConnectorWrapper}; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: RefCell>, + pub(crate) connector: Box, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -153,9 +152,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().finish(), - ))), + connector: Box::new(ConnectorWrapper(Connector::new().finish())), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) @@ -182,8 +179,8 @@ impl Client { { let mut req = ClientRequest::new(method, url, self.0.clone()); - for (key, value) in self.0.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); + for header in self.0.headers.iter() { + req = req.insert_header_if_none(header); } req } @@ -198,8 +195,8 @@ impl Client { >::Error: Into, { let mut req = self.request(head.method.clone(), url); - for (key, value) in head.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); + for header in head.headers.iter() { + req = req.insert_header_if_none(header); } req } diff --git a/awc/src/request.rs b/awc/src/request.rs index 51c3f5190..b9a333b18 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -9,10 +9,10 @@ use serde::Serialize; use actix_http::body::Body; use actix_http::cookie::{Cookie, CookieJar}; -use actix_http::http::header::{self, Header, IntoHeaderValue}; +use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, Method, - Uri, Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, + Version, }; use actix_http::{Error, RequestHead}; @@ -37,13 +37,11 @@ cfg_if::cfg_if! { /// builder-like pattern. /// /// ```rust -/// use actix_rt::System; -/// /// #[actix_rt::main] /// async fn main() { /// let response = awc::Client::new() /// .get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") +/// .insert_header(("User-Agent", "Actix-web")) /// .send() // <- Send http request /// .await; /// @@ -143,110 +141,71 @@ impl ClientRequest { &self.head.peer_addr } - #[inline] /// Returns request's headers. + #[inline] pub fn headers(&self) -> &HeaderMap { &self.head.headers } - #[inline] /// Returns request's mutable headers. + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head.headers } - /// Set a header. - /// - /// ```rust - /// fn main() { - /// # actix_rt::System::new("test").block_on(futures_util::future::lazy(|_| { - /// let req = awc::Client::new() - /// .get("http://www.rust-lang.org") - /// .set(awc::http::header::Date::now()) - /// .set(awc::http::header::ContentType(mime::TEXT_HTML)); - /// # Ok::<_, ()>(()) - /// # })); - /// } - /// ``` - pub fn set(mut self, hdr: H) -> Self { - match hdr.try_into() { - Ok(value) => { - self.head.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// use awc::{http, Client}; - /// - /// fn main() { - /// # actix_rt::System::new("test").block_on(async { - /// let req = Client::new() - /// .get("http://www.rust-lang.org") - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json"); - /// # Ok::<_, ()>(()) - /// # }); - /// } - /// ``` - pub fn header(mut self, key: K, value: V) -> Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), - } - self - } + }; - /// Insert a header, replaces existing header. - pub fn set_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } self } /// Insert a header only if it is not yet set. - pub fn set_header_if_none(mut self, key: K, value: V) -> Self + pub fn insert_header_if_none(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - match HeaderName::try_from(key) { - Ok(key) => { + match header.try_into_header_pair() { + Ok((key, value)) => { if !self.head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - } + self.head.headers.insert(key, value); } } Err(e) => self.err = Some(e.into()), - } + }; + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ```rust + /// # #[actix_rt::main] + /// # async fn main() { + /// # use awc::Client; + /// use awc::http::header::ContentType; + /// + /// Client::new() + /// .get("http://www.rust-lang.org") + /// .insert_header(("X-TEST", "value")) + /// .insert_header(ContentType(mime::APPLICATION_JSON)); + /// # } + /// ``` + pub fn append_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + match header.try_into_header_pair() { + Ok((key, value)) => self.head.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + self } @@ -282,7 +241,7 @@ impl ClientRequest { /// Set content length #[inline] pub fn content_length(self, len: u64) -> Self { - self.header(header::CONTENT_LENGTH, len) + self.append_header((header::CONTENT_LENGTH, len)) } /// Set HTTP basic authorization header @@ -294,10 +253,10 @@ impl ClientRequest { Some(password) => format!("{}:{}", username, password), None => format!("{}:", username), }; - self.header( + self.append_header(( header::AUTHORIZATION, format!("Basic {}", base64::encode(&auth)), - ) + )) } /// Set HTTP bearer authentication header @@ -305,7 +264,7 @@ impl ClientRequest { where T: fmt::Display, { - self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + self.append_header((header::AUTHORIZATION, format!("Bearer {}", token))) } /// Set a cookie @@ -557,12 +516,15 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + slf = + slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - slf = - slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + slf = slf.insert_header_if_none(( + header::ACCEPT_ENCODING, + "gzip, deflate", + )) } }; } @@ -595,7 +557,7 @@ mod tests { #[actix_rt::test] async fn test_debug() { - let request = Client::new().get("/").header("x-test", "111"); + let request = Client::new().get("/").append_header(("x-test", "111")); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); @@ -606,18 +568,18 @@ mod tests { let req = Client::new() .put("/") .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header(header::Date(SystemTime::now().into())) .content_type("plain/text") - .header(header::SERVER, "awc"); + .append_header((header::SERVER, "awc")); let req = if let Some(val) = Some("server") { - req.header(header::USER_AGENT, val) + req.append_header((header::USER_AGENT, val)) } else { req }; let req = if let Some(_val) = Option::<&str>::None { - req.header(header::ALLOW, "1") + req.append_header((header::ALLOW, "1")) } else { req }; @@ -660,7 +622,7 @@ mod tests { .header(header::CONTENT_TYPE, "111") .finish() .get("/") - .set_header(header::CONTENT_TYPE, "222"); + .insert_header((header::CONTENT_TYPE, "222")); assert_eq!( req.head diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ebf87e23b..5f790a038 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -183,15 +183,13 @@ impl RequestSender { where B: Into, { - let mut connector = config.connector.borrow_mut(); - let fut = match self { RequestSender::Owned(head) => { - connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => { - connector.send_request_extra(head, extra_headers, body.into(), addr) + config.connector.send_request(head, body.into(), addr) } + RequestSender::Rc(head, extra_headers) => config + .connector + .send_request_extra(head, extra_headers, body.into(), addr), }; SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) @@ -296,7 +294,7 @@ impl RequestSender { match self { RequestSender::Owned(head) => { if !head.headers.contains_key(&key) { - match value.try_into() { + match value.try_into_value() { Ok(value) => head.headers.insert(key, value), Err(e) => return Err(e.into()), } @@ -306,7 +304,7 @@ impl RequestSender { if !head.headers.contains_key(&key) && !extra_headers.iter().any(|h| h.contains_key(&key)) { - match value.try_into() { + match value.try_into_value() { Ok(v) => { let h = extra_headers.get_or_insert(HeaderMap::new()); h.insert(key, v) diff --git a/awc/src/test.rs b/awc/src/test.rs index 68e5c9dc5..84646b9f7 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -45,7 +45,7 @@ impl TestResponse { /// Set a header pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into() { + if let Ok(value) = hdr.try_into_value() { self.head.headers.append(H::name(), value); return self; } @@ -60,7 +60,7 @@ impl TestResponse { V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { + if let Ok(value) = value.try_into_value() { self.head.headers.append(key, value); return self; } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f747f701f..7c795226b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -170,7 +170,7 @@ impl WebsocketsRequest { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.head.headers.append(key, value); } @@ -189,7 +189,7 @@ impl WebsocketsRequest { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.head.headers.insert(key, value); } @@ -210,7 +210,7 @@ impl WebsocketsRequest { match HeaderName::try_from(key) { Ok(key) => { if !self.head.headers.contains_key(&key) { - match value.try_into() { + match value.try_into_value() { Ok(value) => { self.head.headers.insert(key, value); } @@ -325,11 +325,7 @@ impl WebsocketsRequest { let max_size = self.max_size; let server_mode = self.server_mode; - let fut = self - .config - .connector - .borrow_mut() - .open_tunnel(head, self.addr); + let fut = self.config.connector.open_tunnel(head, self.addr); // set request timeout let (head, framed) = if let Some(to) = self.config.timeout { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 6cae77a49..a9f5dc370 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -9,17 +9,20 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::future::ok; +use futures_util::{future::ok, stream}; use rand::Rng; -use actix_http::HttpService; +use actix_http::{ + http::{self, StatusCode}, + HttpService, +}; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory}; -use actix_web::dev::{AppConfig, BodyEncoding}; -use actix_web::http::Cookie; -use actix_web::middleware::Compress; use actix_web::{ - http::header, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, + dev::{AppConfig, BodyEncoding}, + http::{header, Cookie}, + middleware::Compress, + test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; use awc::error::SendRequestError; @@ -52,7 +55,7 @@ async fn test_simple() { .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); - let request = srv.get("/").header("x-test", "111").send(); + let request = srv.get("/").insert_header(("x-test", "111")).send(); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -82,7 +85,7 @@ async fn test_json() { let request = srv .get("/") - .header("x-test", "111") + .insert_header(("x-test", "111")) .send_json(&"TEST".to_string()); let response = request.await.unwrap(); assert!(response.status().is_success()); @@ -99,7 +102,10 @@ async fn test_form() { 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 request = srv + .get("/") + .append_header(("x-test", "111")) + .send_form(&data); let response = request.await.unwrap(); assert!(response.status().is_success()); } @@ -114,9 +120,7 @@ async fn test_timeout() { }); let connector = awc::Connector::new() - .connector(actix_tls::connect::new_connector( - actix_tls::connect::start_default_resolver().await.unwrap(), - )) + .connector(actix_tls::connect::default_connector()) .timeout(Duration::from_secs(15)) .finish(); @@ -438,7 +442,7 @@ async fn test_client_gzip_encoding() { let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -461,7 +465,7 @@ async fn test_client_gzip_encoding_large() { let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -489,7 +493,7 @@ async fn test_client_gzip_encoding_large_random() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -511,7 +515,7 @@ async fn test_client_brotli_encoding() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "br") + .insert_header(("content-encoding", "br")) .body(data) }))) }); @@ -539,7 +543,7 @@ async fn test_client_brotli_encoding_large_random() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "br") + .insert_header(("content-encoding", "br")) .body(data) }))) }); @@ -554,113 +558,94 @@ async fn test_client_brotli_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -// #[actix_rt::test] -// async fn test_client_deflate_encoding() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(STR) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.post("/").send_body(STR); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); -// #[actix_rt::test] -// async fn test_client_deflate_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&rand::distributions::Alphanumeric) -// .take(70_000) -// .collect::(); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .map(char::from) + .take(70_000) + .collect::(); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(data.clone()) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + let req = srv.post("/").send_body(data.clone()); -// #[actix_rt::test] -// async fn test_client_streaming_explicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .map_err(Error::from) -// .and_then(|body| { -// Ok(HttpResponse::Ok() -// .chunked() -// .content_encoding(http::ContentEncoding::Identity) -// .body(body)) -// }) -// .responder() -// }) -// }); + let mut res = req.await.unwrap(); + let bytes = res.body().await.unwrap(); -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(bytes, Bytes::from(data)); +} -// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); +#[actix_rt::test] +async fn test_client_streaming_explicit() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: web::Payload| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .streaming(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let body = stream::once(async { + Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) + }); + let req = srv.post("/").send_stream(Box::pin(body)); -// #[actix_rt::test] -// async fn test_body_streaming_implicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|_| { -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); -// HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Gzip) -// .body(Body::Streaming(Box::new(body))) -// }) -// }); + let mut res = req.await.unwrap(); + assert!(res.status().is_success()); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } +#[actix_rt::test] +async fn test_body_streaming_implicit() { + let srv = test::start(|| { + App::new().default_service(web::to(|| { + let body = stream::once(async { + Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) + }); + + HttpResponse::Ok() + .encoding(http::ContentEncoding::Gzip) + .streaming(Box::pin(body)) + })) + }); + + let req = srv.get("/").send(); + + let mut res = req.await.unwrap(); + assert!(res.status().is_success()); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} #[actix_rt::test] async fn test_client_cookie_handling() { @@ -731,35 +716,35 @@ async fn test_client_cookie_handling() { assert_eq!(c2, cookie2); } -// #[actix_rt::test] -// fn client_read_until_eof() { -// let addr = test::TestServer::unused_addr(); +#[actix_rt::test] +async fn client_unread_response() { + let addr = test::unused_addr(); -// thread::spawn(move || { -// let lst = net::TcpListener::bind(addr).unwrap(); + let lst = std::net::TcpListener::bind(addr).unwrap(); -// for stream in lst.incoming() { -// let mut stream = stream.unwrap(); -// let mut b = [0; 1000]; -// let _ = stream.read(&mut b).unwrap(); -// let _ = stream -// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); -// } -// }); + std::thread::spawn(move || { + for stream in lst.incoming() { + let mut stream = stream.unwrap(); + let mut b = [0; 1000]; + let _ = stream.read(&mut b).unwrap(); + let _ = stream.write_all( + b"HTTP/1.1 200 OK\r\n\ + connection: close\r\n\ + \r\n\ + welcome!", + ); + } + }); -// let mut sys = actix::System::new("test"); + // client request + let req = awc::Client::new().get(format!("http://{}/", addr).as_str()); + let mut res = req.send().await.unwrap(); + assert!(res.status().is_success()); -// // client request -// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) -// .finish() -// .unwrap(); -// let response = req.send().await.unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = response.body().await.unwrap(); -// assert_eq!(bytes, Bytes::from_static(b"welcome!")); -// } + // awc does not read all bytes unless content-length is specified + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"")); +} #[actix_rt::test] async fn client_basic_auth() { diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 3fa76d4a9..2da3d9696 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,57 +1,57 @@ #![cfg(feature = "rustls")] -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; + +extern crate rust_tls as rustls; + +use std::{ + io::BufReader, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; -use actix_web::http::Version; -use actix_web::{dev::AppConfig, web, App, HttpResponse}; +use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use futures_util::future::ok; -use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode}; -use rust_tls::ClientConfig; +use rustls::internal::pemfile::{certs, pkcs8_private_keys}; +use rustls::{ClientConfig, NoClientAuth, ServerConfig}; -#[allow(unused)] -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(open_ssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2").unwrap(); - builder.build() +fn tls_config() -> ServerConfig { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + + 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(); + + config } mod danger { - pub struct NoCertificateVerification {} + pub struct NoCertificateVerification; - impl rust_tls::ServerCertVerifier for NoCertificateVerification { + impl rustls::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rust_tls::RootCertStore, - _presented_certs: &[rust_tls::Certificate], + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], _dns_name: webpki::DNSNameRef<'_>, _ocsp: &[u8], - ) -> Result { - Ok(rust_tls::ServerCertVerified::assertion()) + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) } } } -// #[actix_rt::test] -async fn _test_connection_reuse_h2() { +#[actix_rt::test] +async fn test_connection_reuse_h2() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -68,19 +68,19 @@ async fn _test_connection_reuse_h2() { .service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .rustls(tls_config()) .map_err(|_| ()), ) }) .await; - // disable ssl verification + // disable TLS verification let mut config = ClientConfig::new(); let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; config.set_protocols(&protos); config .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) diff --git a/benches/responder.rs b/benches/responder.rs index 61180d575..8cfdbd3ea 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -68,7 +68,7 @@ impl Responder for OptionResponder { } fn future_responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("future_responder", move |b| { @@ -91,7 +91,7 @@ fn future_responder(c: &mut Criterion) { } fn responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("responder", move |b| { b.iter_custom(|_| { diff --git a/benches/server.rs b/benches/server.rs index 117b6136e..ce79f077d 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -29,7 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn bench_async_burst(c: &mut Criterion) { // We are using System here, since Runtime requires preinitialized tokio // Maybe add to actix_rt docs - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = rt.block_on(async { test::start(|| { diff --git a/benches/service.rs b/benches/service.rs index 8ca6cbe28..0d3264857 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -25,7 +25,7 @@ pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) where S: Service + 'static, { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); @@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result { // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] fn async_web_service(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), )))); diff --git a/examples/client.rs b/examples/client.rs index 15cf24fa8..b9574590d 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> { // Create request builder, configure request and send let mut response = client .get("https://www.rust-lang.org/") - .header("User-Agent", "Actix-web") + .append_header(("User-Agent", "Actix-web")) .send() .await?; diff --git a/src/app.rs b/src/app.rs index fcb491a21..1660c4b94 100644 --- a/src/app.rs +++ b/src/app.rs @@ -34,7 +34,6 @@ pub struct App { services: Vec>, default: Option>, factory_ref: Rc>>, - data: Vec>, data_factories: Vec, external: Vec, extensions: Extensions, @@ -48,7 +47,6 @@ impl App { let fref = Rc::new(RefCell::new(None)); App { endpoint: AppEntry::new(fref.clone()), - data: Vec::new(), data_factories: Vec::new(), services: Vec::new(), default: None, @@ -101,9 +99,8 @@ where /// web::resource("/index.html").route( /// web::get().to(index))); /// ``` - pub fn data(mut self, data: U) -> Self { - self.data.push(Box::new(Data::new(data))); - self + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) } /// Set application data factory. This function is @@ -157,8 +154,7 @@ where /// some of the resource's configuration could be moved to different module. /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; + /// use actix_web::{web, App, HttpResponse}; /// /// // this function could be located in different module /// fn config(cfg: &mut web::ServiceConfig) { @@ -168,12 +164,9 @@ where /// ); /// } /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } + /// App::new() + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// ``` pub fn configure(mut self, f: F) -> Self where @@ -181,10 +174,9 @@ where { let mut cfg = ServiceConfig::new(); f(&mut cfg); - self.data.extend(cfg.data); self.services.extend(cfg.services); self.external.extend(cfg.external); - self.extensions.extend(cfg.extensions); + self.extensions.extend(cfg.app_data); self } @@ -374,7 +366,6 @@ where { App { endpoint: apply(mw, self.endpoint), - data: self.data, data_factories: self.data_factories, services: self.services, default: self.default, @@ -431,12 +422,11 @@ where > where B1: MessageBody, - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future, Error>>, { App { endpoint: apply_fn_factory(self.endpoint, mw), - data: self.data, data_factories: self.data_factories, services: self.services, default: self.default, @@ -462,7 +452,6 @@ where { fn into_factory(self) -> AppInit { AppInit { - data_factories: self.data.into_boxed_slice().into(), async_data_factories: self.data_factories.into_boxed_slice().into(), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), @@ -491,7 +480,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::resource("/test").to(HttpResponse::Ok)), ) .await; @@ -503,7 +492,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").to(HttpResponse::Ok)) .service( @@ -536,7 +525,7 @@ mod tests { #[actix_rt::test] async fn test_data_factory() { - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -545,7 +534,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -568,7 +557,7 @@ mod tests { #[actix_rt::test] async fn test_extension() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 10); HttpResponse::Ok() @@ -582,7 +571,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap( DefaultHeaders::new() @@ -592,7 +581,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -602,7 +591,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap( @@ -612,7 +601,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -622,7 +611,7 @@ mod tests { #[actix_rt::test] async fn test_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let fut = srv.call(req); @@ -639,7 +628,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -649,7 +638,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap_fn(|req, srv| { @@ -666,7 +655,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -676,7 +665,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .external_resource("youtube", "https://youtube.com/watch/{video_id}") .route( @@ -690,7 +679,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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 8169be517..a38f1d652 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -10,7 +10,7 @@ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; use crate::config::{AppConfig, AppService}; -use crate::data::{DataFactory, FnDataFactory}; +use crate::data::FnDataFactory; use crate::error::Error; use crate::guard::Guard; use crate::request::{HttpRequest, HttpRequestPool}; @@ -35,7 +35,6 @@ where { pub(crate) endpoint: T, pub(crate) extensions: RefCell>, - pub(crate) data_factories: Rc<[Box]>, pub(crate) async_data_factories: Rc<[FnDataFactory]>, pub(crate) services: Rc>>>, pub(crate) default: Option>, @@ -71,8 +70,7 @@ where }); // App config - let mut config = - AppService::new(config, default.clone(), self.data_factories.clone()); + let mut config = AppService::new(config, default.clone()); // register services std::mem::take(&mut *self.services.borrow_mut()) @@ -119,8 +117,6 @@ where .take() .unwrap_or_else(Extensions::new); - let data_factories = self.data_factories.clone(); - Box::pin(async move { // async data factories let async_data_factories = factory_futs @@ -133,12 +129,9 @@ where let service = endpoint_fut.await?; // populate app data container from (async) data factories. - data_factories - .iter() - .chain(&async_data_factories) - .for_each(|factory| { - factory.create(&mut app_data); - }); + async_data_factories.iter().for_each(|factory| { + factory.create(&mut app_data); + }); Ok(AppInitService { service, @@ -149,7 +142,7 @@ where } } -/// Service to convert `Request` to a `ServiceRequest` +/// Service that takes a [`Request`] and delegates to a service that take a [`ServiceRequest`]. pub struct AppInitService where T: Service, Error = Error>, @@ -159,7 +152,7 @@ where app_state: Rc, } -// a collection of AppInitService state that shared between HttpRequests. +/// A collection of [`AppInitService`] state that shared across `HttpRequest`s. pub(crate) struct AppInitServiceState { rmap: Rc, config: AppConfig, @@ -203,7 +196,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.app_state.pool().pop() { @@ -294,8 +287,8 @@ impl Service for AppRouting { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -361,7 +354,7 @@ mod tests { let data = Arc::new(AtomicBool::new(false)); { - let mut app = init_service( + let app = init_service( App::new() .data(DropData(data.clone())) .service(web::resource("/test").to(HttpResponse::Ok)), diff --git a/src/config.rs b/src/config.rs index 2b93ae892..24afca295 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; -use crate::data::{Data, DataFactory}; +use crate::data::Data; use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; @@ -31,20 +31,14 @@ pub struct AppService { Option, Option>, )>, - service_data: Rc<[Box]>, } impl AppService { - /// Crate server settings instance - pub(crate) fn new( - config: AppConfig, - default: Rc, - service_data: Rc<[Box]>, - ) -> Self { + /// Crate server settings instance. + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { AppService { config, default, - service_data, root: true, services: Vec::new(), } @@ -75,7 +69,6 @@ impl AppService { default: self.default.clone(), services: Vec::new(), root: false, - service_data: self.service_data.clone(), } } @@ -89,15 +82,7 @@ impl AppService { self.default.clone() } - /// Set global route data - pub fn set_service_data(&self, extensions: &mut Extensions) -> bool { - for f in self.service_data.iter() { - f.create(extensions); - } - !self.service_data.is_empty() - } - - /// Register http service + /// Register HTTP service. pub fn register_service( &mut self, rdef: ResourceDef, @@ -168,47 +153,60 @@ impl Default for AppConfig { } } -/// Service config is used for external configuration. -/// Part of application configuration could be offloaded -/// to set of external methods. This could help with -/// modularization of big application configuration. +/// Enables parts of app configuration to be declared separately from the app itself. Helpful for +/// modularizing large applications. +/// +/// Merge a `ServiceConfig` into an app using [`App::configure`](crate::App::configure). Scope and +/// resources services have similar methods. +/// +/// ``` +/// use actix_web::{web, App, HttpResponse}; +/// +/// // this function could be located in different module +/// fn config(cfg: &mut web::ServiceConfig) { +/// cfg.service(web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// +/// // merge `/test` routes from config function to App +/// App::new().configure(config); +/// ``` pub struct ServiceConfig { pub(crate) services: Vec>, - pub(crate) data: Vec>, pub(crate) external: Vec, - pub(crate) extensions: Extensions, + pub(crate) app_data: Extensions, } impl ServiceConfig { pub(crate) fn new() -> Self { Self { services: Vec::new(), - data: Vec::new(), external: Vec::new(), - extensions: Extensions::new(), + app_data: Extensions::new(), } } - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. + /// Add shared app data item. /// - /// This is same as `App::data()` method. - pub fn data(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(Data::new(data))); + /// Counterpart to [`App::data()`](crate::App::data). + pub fn data(&mut self, data: U) -> &mut Self { + self.app_data(Data::new(data)); self } - /// Set arbitrary data item. + /// Add arbitrary app data item. /// - /// This is same as `App::data()` method. + /// Counterpart to [`App::app_data()`](crate::App::app_data). pub fn app_data(&mut self, ext: U) -> &mut Self { - self.extensions.insert(ext); + self.app_data.insert(ext); self } /// Configure route for a specific path. /// - /// This is same as `App::route()` method. + /// Counterpart to [`App::route()`](crate::App::route). pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self { self.service( Resource::new(path) @@ -217,9 +215,9 @@ impl ServiceConfig { ) } - /// Register http service. + /// Register HTTP service factory. /// - /// This is same as `App::service()` method. + /// Counterpart to [`App::service()`](crate::App::service). pub fn service(&mut self, factory: F) -> &mut Self where F: HttpServiceFactory + 'static, @@ -231,11 +229,11 @@ impl ServiceConfig { /// Register an external resource. /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. + /// External resources are useful for URL generation purposes only and are never considered for + /// matching at request time. Calls to [`HttpRequest::url_for()`](crate::HttpRequest::url_for) + /// will work as expected. /// - /// This is same as `App::external_service()` method. + /// Counterpart to [`App::external_resource()`](crate::App::external_resource). pub fn external_resource(&mut self, name: N, url: U) -> &mut Self where N: AsRef, @@ -265,7 +263,7 @@ mod tests { cfg.app_data(15u8); }; - let mut srv = init_service(App::new().configure(cfg).service( + let srv = init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data, req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 15u8); HttpResponse::Ok() @@ -288,7 +286,7 @@ mod tests { // }); // }; - // let mut srv = + // let srv = // init_service(App::new().configure(cfg).service( // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), // )); @@ -299,7 +297,7 @@ mod tests { // let cfg2 = |cfg: &mut ServiceConfig| { // cfg.data_factory(|| Ok::<_, ()>(10u32)); // }; - // let mut srv = init_service( + // let srv = init_service( // App::new() // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) // .configure(cfg2), @@ -311,7 +309,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .configure(|cfg| { cfg.external_resource( @@ -330,7 +328,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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")); @@ -338,7 +336,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service(App::new().configure(|cfg| { + let srv = init_service(App::new().configure(|cfg| { cfg.service( web::resource("/test").route(web::get().to(HttpResponse::Created)), ) @@ -349,13 +347,13 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/data.rs b/src/data.rs index 19c258ff0..12a1f5cf8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -10,8 +10,9 @@ use crate::dev::Payload; use crate::extract::FromRequest; use crate::request::HttpRequest; -/// Application data factory +/// Data factory. pub(crate) trait DataFactory { + /// Return true if modifications were made to extensions map. fn create(&self, extensions: &mut Extensions) -> bool; } @@ -126,12 +127,8 @@ impl FromRequest for Data { impl DataFactory for Data { fn create(&self, extensions: &mut Extensions) -> bool { - if !extensions.contains::>() { - extensions.insert(Data(self.0.clone())); - true - } else { - false - } + extensions.insert(Data(self.0.clone())); + true } } @@ -147,7 +144,7 @@ mod tests { #[actix_rt::test] async fn test_data_extractor() { - let mut srv = init_service(App::new().data("TEST".to_string()).service( + let srv = init_service(App::new().data("TEST".to_string()).service( web::resource("/").to(|data: web::Data| { assert_eq!(data.to_lowercase(), "test"); HttpResponse::Ok() @@ -159,7 +156,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -167,11 +164,29 @@ mod tests { let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + + let srv = init_service( + App::new() + .data(10u32) + .data(13u32) + .app_data(12u64) + .app_data(15u64) + .default_service(web::to(|n: web::Data, req: HttpRequest| { + // in each case, the latter insertion should be preserved + assert_eq!(*req.app_data::().unwrap(), 15); + assert_eq!(*n.into_inner(), 13); + HttpResponse::Ok() + })), + ) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_app_data_extractor() { - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -181,7 +196,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -193,7 +208,7 @@ mod tests { #[actix_rt::test] async fn test_route_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10usize) @@ -207,7 +222,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); // different type - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10u32) @@ -222,7 +237,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::resource("/").data(10usize).route(web::get().to( |data: web::Data| { assert_eq!(**data, 10); diff --git a/src/extract.rs b/src/extract.rs index 4081188ef..7a677bca4 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -347,25 +347,21 @@ mod tests { #[actix_rt::test] async fn test_option() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .data(FormConfig::default().limit(4096)) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .data(FormConfig::default().limit(4096)) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await .unwrap(); assert_eq!(r, None); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "9")) + .set_payload(Bytes::from_static(b"hello=world")) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await @@ -377,13 +373,11 @@ mod tests { })) ); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "9")) + .set_payload(Bytes::from_static(b"bye=world")) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await @@ -393,13 +387,11 @@ mod tests { #[actix_rt::test] async fn test_result() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "11")) + .set_payload(Bytes::from_static(b"hello=world")) + .to_http_parts(); let r = Result::, Error>::from_request(&req, &mut pl) .await @@ -412,13 +404,11 @@ mod tests { }) ); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, 9)) + .set_payload(Bytes::from_static(b"bye=world")) + .to_http_parts(); let r = Result::, Error>::from_request(&req, &mut pl) .await diff --git a/src/guard.rs b/src/guard.rs index 25284236a..ba0cbea85 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -330,7 +330,8 @@ mod tests { #[test] fn test_header() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked") + let req = TestRequest::default() + .insert_header((header::TRANSFER_ENCODING, "chunked")) .to_http_request(); let pred = Header("transfer-encoding", "chunked"); @@ -346,10 +347,10 @@ mod tests { #[test] fn test_host() { let req = TestRequest::default() - .header( + .insert_header(( header::HOST, header::HeaderValue::from_static("www.rust-lang.org"), - ) + )) .to_http_request(); let pred = Host("www.rust-lang.org"); @@ -374,10 +375,10 @@ mod tests { #[test] fn test_host_scheme() { let req = TestRequest::default() - .header( + .insert_header(( header::HOST, header::HeaderValue::from_static("https://www.rust-lang.org"), - ) + )) .to_http_request(); let pred = Host("www.rust-lang.org").scheme("https"); diff --git a/src/handler.rs b/src/handler.rs index 47656cd84..0016b741e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -101,7 +101,7 @@ where } } -// HandlerService is both it's ServiceFactory and Service Type. +/// HandlerService is both it's ServiceFactory and Service Type. impl Service for HandlerService where F: Handler, @@ -113,11 +113,11 @@ where type Error = Error; type Future = HandlerServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) diff --git a/src/info.rs b/src/info.rs index 75ebf67eb..c9ddf6ec4 100644 --- a/src/info.rs +++ b/src/info.rs @@ -200,10 +200,10 @@ mod tests { assert_eq!(info.host(), "localhost:8080"); let req = TestRequest::default() - .header( + .insert_header(( header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ) + )) .to_http_request(); let info = req.connection_info(); @@ -212,7 +212,7 @@ mod tests { assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); let req = TestRequest::default() - .header(header::HOST, "rust-lang.org") + .insert_header((header::HOST, "rust-lang.org")) .to_http_request(); let info = req.connection_info(); @@ -221,20 +221,20 @@ mod tests { assert_eq!(info.realip_remote_addr(), None); let req = TestRequest::default() - .header(X_FORWARDED_FOR, "192.0.2.60") + .insert_header((X_FORWARDED_FOR, "192.0.2.60")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); let req = TestRequest::default() - .header(X_FORWARDED_HOST, "192.0.2.60") + .insert_header((X_FORWARDED_HOST, "192.0.2.60")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.host(), "192.0.2.60"); assert_eq!(info.realip_remote_addr(), None); let req = TestRequest::default() - .header(X_FORWARDED_PROTO, "https") + .insert_header((X_FORWARDED_PROTO, "https")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.scheme(), "https"); diff --git a/src/lib.rs b/src/lib.rs index fa4e70aec..52471f4b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub mod client { //! //! // Create request builder and send request //! let response = client.get("http://www.rust-lang.org") - //! .header("User-Agent", "actix-web/3.0") + //! .insert_header(("User-Agent", "actix-web/3.0")) //! .send() // <- Send request //! .await; // <- Wait for response //! diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index eabd1190d..2df535280 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -80,11 +80,11 @@ where type Error = Error; type Future = CompatMiddlewareFuture; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx).map_err(From::from) } - fn call(&mut self, req: Req) -> Self::Future { + fn call(&self, req: Req) -> Self::Future { let fut = self.service.call(req); CompatMiddlewareFuture { fut } } @@ -110,8 +110,7 @@ where } } -// trait for convert ServiceResponse's ResponseBody generic type -// to ResponseBody +/// Convert `ServiceResponse`'s `ResponseBody` generic type to `ResponseBody`. pub trait MapServiceResponseBody { fn map_body(self) -> ServiceResponse; } @@ -139,7 +138,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap(Compat::new(logger)) @@ -152,7 +151,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -161,7 +160,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("app/test") .wrap(Compat::new(logger)) @@ -172,7 +171,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -186,11 +185,11 @@ mod tests { let logger = Logger::default(); - let mut mw = Condition::new(true, Compat::new(logger)) + let mw = Condition::new(true, Compat::new(logger)) .new_transform(srv.into_service()) .await .unwrap(); - let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await; + let resp = call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 376719ab2..7a45e4c8d 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,6 +16,7 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use futures_core::ready; use futures_util::future::{ok, Ready}; use pin_project::pin_project; @@ -89,7 +90,7 @@ where actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { @@ -131,7 +132,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - match futures_util::ready!(this.fut.poll(cx)) { + match ready!(this.fut.poll(cx)) { Ok(resp) => { let enc = if let Some(enc) = resp.response().get_encoding() { enc diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index d61e7d576..85eba3fb8 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -76,14 +76,14 @@ where type Error = E::Error; type Future = Either; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { match self { ConditionMiddleware::Enable(service) => service.poll_ready(cx), ConditionMiddleware::Disable(service) => service.poll_ready(cx), } } - fn call(&mut self, req: Req) -> Self::Future { + fn call(&self, req: Req) -> Self::Future { match self { ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), @@ -123,12 +123,12 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(true, mw) + let mw = Condition::new(true, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -141,13 +141,13 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(false, mw) + let mw = Condition::new(false, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 6f027124f..a9a50ec2c 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -143,7 +143,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let fut = self.service.call(req); @@ -200,7 +200,7 @@ mod tests { #[actix_rt::test] async fn test_default_headers() { - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(ok_service()) .await @@ -212,10 +212,13 @@ mod tests { let req = TestRequest::default().to_srv_request(); let srv = |req: ServiceRequest| { - ok(req - .into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())) + ok(req.into_response( + HttpResponse::Ok() + .insert_header((CONTENT_TYPE, "0002")) + .finish(), + )) }; - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(srv.into_service()) .await @@ -228,7 +231,7 @@ mod tests { async fn test_content_type() { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .add_content_type() .new_transform(srv.into_service()) .await diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 44962aa98..70933241d 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -123,7 +123,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); let fut = self.service.call(req); ErrorHandlersFuture::ServiceFuture { fut, handlers } @@ -196,14 +196,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -223,14 +223,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 276265a58..969aa9be5 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -219,7 +219,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) || self.inner.exclude_regex.is_match(req.path()) { @@ -603,19 +603,20 @@ mod tests { let srv = |req: ServiceRequest| { ok(req.into_response( HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") + .insert_header(("X-Test", "ttt")) .finish(), )) }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .to_srv_request(); let _res = srv.call(req).await; } @@ -624,32 +625,34 @@ mod tests { let srv = |req: ServiceRequest| { ok(req.into_response( HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") + .insert_header(("X-Test", "ttt")) .finish(), )) }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") .exclude_regex("\\w"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .to_srv_request(); let _res = srv.call(req).await.unwrap(); } #[actix_rt::test] async fn test_url_path() { let mut format = Format::new("%T %U"); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .uri("/test/route/yeah") - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .uri("/test/route/yeah") + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { @@ -676,12 +679,13 @@ mod tests { async fn test_default_format() { let mut format = Format::default(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .peer_addr("127.0.0.1:8081".parse().unwrap()) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .peer_addr("127.0.0.1:8081".parse().unwrap()) + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { @@ -736,13 +740,14 @@ mod tests { async fn test_remote_addr_format() { let mut format = Format::new("%{r}a"); - let req = TestRequest::with_header( - header::FORWARDED, - header::HeaderValue::from_static( - "for=192.0.2.60;proto=http;by=203.0.113.43", - ), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::FORWARDED, + header::HeaderValue::from_static( + "for=192.0.2.60;proto=http;by=203.0.113.43", + ), + )) + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { @@ -801,7 +806,7 @@ mod tests { captured.to_owned() }); - let mut srv = logger.new_transform(test::ok_service()).await.unwrap(); + let srv = logger.new_transform(test::ok_service()).await.unwrap(); let req = TestRequest::default().to_srv_request(); srv.call(req).await.unwrap(); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 89cab9073..8519f041a 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -57,7 +57,7 @@ impl Default for TrailingSlash { /// ```rust /// use actix_web::{web, middleware, App}; /// -/// # actix_web::rt::System::new("doctest").block_on(async { +/// # actix_web::rt::System::new().block_on(async { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) /// .route("/test", web::get().to(|| async { "test" })) @@ -66,22 +66,22 @@ impl Default for TrailingSlash { /// use actix_web::http::StatusCode; /// use actix_web::test::{call_service, init_service, TestRequest}; /// -/// let mut app = init_service(app).await; +/// let app = init_service(app).await; /// /// let req = TestRequest::with_uri("/test").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/test/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/unmatchable").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// /// let req = TestRequest::with_uri("/unmatchable/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # }) /// ``` @@ -132,7 +132,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + fn call(&self, mut req: ServiceRequest) -> Self::Future { let head = req.head_mut(); let original_path = head.uri.path(); @@ -195,7 +195,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) @@ -204,37 +204,37 @@ mod tests { .await; let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("//v1/something").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//////something").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("/v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn trim_trailing_slashes() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::Trim)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -244,37 +244,37 @@ mod tests { // root paths should still work let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1/something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("/v1/something/").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//something//").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("//v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn keep_trailing_slash_unchanged() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::MergeOnly)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -299,7 +299,7 @@ mod tests { for (path, success) in tests { let req = TestRequest::with_uri(path).to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert_eq!(res.status().is_success(), success); } } @@ -311,7 +311,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -342,7 +342,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -359,7 +359,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); diff --git a/src/request.rs b/src/request.rs index c0e26006c..a563518e0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -423,8 +423,9 @@ mod tests { #[test] fn test_debug() { - let req = - TestRequest::with_header("content-type", "text/plain").to_http_request(); + let req = TestRequest::default() + .insert_header(("content-type", "text/plain")) + .to_http_request(); let dbg = format!("{:?}", req); assert!(dbg.contains("HttpRequest")); } @@ -438,8 +439,8 @@ mod tests { #[test] fn test_request_cookies() { let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") + .append_header((header::COOKIE, "cookie1=value1")) + .append_header((header::COOKIE, "cookie2=value2")) .to_http_request(); { let cookies = req.cookies().unwrap(); @@ -476,7 +477,8 @@ mod tests { assert!(rmap.has_resource("/user/test.html")); assert!(!rmap.has_resource("/test/unknown")); - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + let req = TestRequest::default() + .insert_header((header::HOST, "www.rust-lang.org")) .rmap(rmap) .to_http_request(); @@ -506,7 +508,7 @@ mod tests { assert!(rmap.has_resource("/index.html")); let req = TestRequest::with_uri("/test") - .header(header::HOST, "www.rust-lang.org") + .insert_header((header::HOST, "www.rust-lang.org")) .rmap(rmap) .to_http_request(); let url = req.url_for_static("index"); @@ -554,17 +556,17 @@ mod tests { #[actix_rt::test] async fn test_drop_http_request_pool() { - let mut srv = init_service(App::new().service(web::resource("/").to( + let srv = init_service(App::new().service(web::resource("/").to( |req: HttpRequest| { HttpResponse::Ok() - .set_header("pool_cap", req.app_state().pool().cap) + .insert_header(("pool_cap", req.app_state().pool().cap)) .finish() }, ))) .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; drop(srv); @@ -573,7 +575,7 @@ mod tests { #[actix_rt::test] async fn test_data() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -585,10 +587,10 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service(App::new().app_data(10u32).service( + let srv = init_service(App::new().app_data(10u32).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -600,7 +602,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -612,7 +614,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -643,7 +645,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -683,7 +685,7 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), @@ -694,7 +696,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -703,7 +705,7 @@ mod tests { #[actix_rt::test] async fn extract_path_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/user/{id}") .service(web::resource("/profile").route(web::get().to( @@ -725,17 +727,17 @@ mod tests { .await; let req = TestRequest::get().uri("/user/22/profile").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/user/22/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } #[actix_rt::test] async fn extract_path_pattern_complex() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::scope("/user").service(web::scope("/{id}").service( web::resource("").to(move |req: HttpRequest| { @@ -757,15 +759,15 @@ mod tests { .await; let req = TestRequest::get().uri("/user/test").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } } diff --git a/src/request_data.rs b/src/request_data.rs index 285154884..beee8ac12 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -102,7 +102,7 @@ mod tests { #[actix_rt::test] async fn req_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { if req.method() == Method::POST { @@ -142,7 +142,7 @@ mod tests { #[actix_rt::test] async fn req_data_internal_mutability() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let data_before = Rc::new(RefCell::new(42u32)); diff --git a/src/resource.rs b/src/resource.rs index 843237079..188e6fa43 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -203,10 +203,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.app_data.is_none() { - self.app_data = Some(Extensions::new()); - } - self.app_data.as_mut().unwrap().insert(data); + self.app_data + .get_or_insert_with(Extensions::new) + .insert(data); + self } @@ -328,7 +328,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Resource { @@ -382,18 +382,16 @@ where } else { Some(std::mem::take(&mut self.guards)) }; + let mut rdef = if config.is_root() || !self.rdef.is_empty() { ResourceDef::new(insert_slash(self.rdef.clone())) } else { ResourceDef::new(self.rdef.clone()) }; + if let Some(ref name) = self.name { *rdef.name_mut() = name.clone(); } - // custom app data storage - if let Some(ref mut ext) = self.app_data { - config.set_service_data(ext); - } config.register_service(rdef, guards, self, None) } @@ -473,18 +471,21 @@ impl Service for ResourceService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - for route in self.routes.iter_mut() { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + for route in self.routes.iter() { if route.check(&mut req) { if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); } + return route.call(req); } } + if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); } + self.default.call(req) } } @@ -529,7 +530,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = + let srv = init_service( App::new().service( web::resource("/test") @@ -543,7 +544,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -553,7 +554,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .wrap_fn(|req, srv| { @@ -573,7 +574,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -583,20 +584,20 @@ mod tests { #[actix_rt::test] async fn test_to() { - let mut srv = + let srv = init_service(App::new().service(web::resource("/test").to(|| async { sleep(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; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource(["/test", "/test2"]) .to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }), @@ -604,16 +605,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test2").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))) .default_service(|r: ServiceRequest| { @@ -622,16 +623,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .route(web::get().to(HttpResponse::Ok)) @@ -643,19 +644,19 @@ mod tests { .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] async fn test_resource_guards() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test/{p}") @@ -678,25 +679,25 @@ mod tests { let req = TestRequest::with_uri("/test/it") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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).await; + let resp = call_service(&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).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NO_CONTENT); } #[actix_rt::test] async fn test_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(1.0f64) .data(1usize) @@ -722,13 +723,13 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_data_default_service() { - let mut srv = init_service( + let srv = init_service( App::new().data(1usize).service( web::resource("/test") .data(10usize) @@ -741,7 +742,7 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/responder.rs b/src/responder.rs index 9b33ac81a..dcad45e0f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,17 +1,17 @@ -use std::convert::TryFrom; +use std::fmt; -use actix_http::error::InternalError; -use actix_http::http::{ - header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode, +use actix_http::{ + error::InternalError, + http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, + ResponseBuilder, }; -use actix_http::ResponseBuilder; use bytes::{Bytes, BytesMut}; use crate::{Error, HttpRequest, HttpResponse}; -/// Trait implemented by types that can be converted to a http response. +/// Trait implemented by types that can be converted to an HTTP response. /// -/// Types that implement this trait can be used as the return type of a handler. +/// Any types that implement this trait can be used in the return type of a handler. pub trait Responder { /// Convert self to `HttpResponse`. fn respond_to(self, req: &HttpRequest) -> HttpResponse; @@ -19,12 +19,11 @@ pub trait Responder { /// Override a status code for a Responder. /// /// ```rust - /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// use actix_web::{http::StatusCode, HttpRequest, Responder}; /// /// fn index(req: HttpRequest) -> impl Responder { /// "Welcome!".with_status(StatusCode::OK) /// } - /// # fn main() {} /// ``` fn with_status(self, status: StatusCode) -> CustomResponder where @@ -33,7 +32,9 @@ pub trait Responder { CustomResponder::new(self).with_status(status) } - /// Add header to the Responder's response. + /// Insert header to the final response. + /// + /// Overrides other headers with the same name. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; @@ -45,21 +46,16 @@ pub trait Responder { /// } /// /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") + /// web::Json(MyObj { name: "Name".to_owned() }) + /// .with_header(("x-version", "1.2.3")) /// } - /// # fn main() {} /// ``` - fn with_header(self, key: K, value: V) -> CustomResponder + fn with_header(self, header: H) -> CustomResponder where Self: Sized, - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - CustomResponder::new(self).with_header(key, value) + CustomResponder::new(self).with_header(header) } } @@ -155,7 +151,7 @@ impl Responder for BytesMut { } } -/// Allows to override status code and headers for a responder. +/// Allows overriding status code and headers for a responder. pub struct CustomResponder { responder: T, status: Option, @@ -181,14 +177,15 @@ impl CustomResponder { /// fn index(req: HttpRequest) -> impl Responder { /// "Welcome!".with_status(StatusCode::OK) /// } - /// # fn main() {} /// ``` pub fn with_status(mut self, status: StatusCode) -> Self { self.status = Some(status); self } - /// Add header to the Responder's response. + /// Insert header to the final response. + /// + /// Overrides other headers with the same name. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; @@ -200,32 +197,24 @@ impl CustomResponder { /// } /// /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") + /// web::Json(MyObj { name: "Name".to_string() }) + /// .with_header(("x-version", "1.2.3")) + /// .with_header(("x-version", "1.2.3")) /// } - /// # fn main() {} /// ``` - pub fn with_header(mut self, key: K, value: V) -> Self + pub fn with_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { if self.headers.is_none() { self.headers = Some(HeaderMap::new()); } - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.headers.as_mut().unwrap().append(key, value); - } - Err(e) => self.error = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value), Err(e) => self.error = Some(e.into()), }; + self } } @@ -240,6 +229,7 @@ impl Responder for CustomResponder { if let Some(ref headers) = self.headers { for (k, v) in headers { + // TODO: before v4, decide if this should be append instead res.headers_mut().insert(k.clone(), v.clone()); } } @@ -250,7 +240,7 @@ impl Responder for CustomResponder { impl Responder for InternalError where - T: std::fmt::Debug + std::fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from_error(self.into()) @@ -270,7 +260,7 @@ pub(crate) mod tests { #[actix_rt::test] async fn test_option_responder() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/none").to(|| async { Option::<&'static str>::None }), @@ -412,7 +402,7 @@ pub(crate) mod tests { let res = "test" .to_string() - .with_header("content-type", "json") + .with_header(("content-type", "json")) .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); @@ -432,13 +422,13 @@ pub(crate) mod tests { let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::OK) - .with_header("content-type", "json") + .with_header((CONTENT_TYPE, mime::APPLICATION_JSON)) .respond_to(&req); 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") + HeaderValue::from_static("application/json") ); } } diff --git a/src/route.rs b/src/route.rs index 8a3d1da9f..c5e297411 100644 --- a/src/route.rs +++ b/src/route.rs @@ -121,11 +121,11 @@ impl Service for RouteService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { self.service.call(req) } } @@ -299,11 +299,11 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { Box::pin(self.service.call(req)) } } @@ -327,7 +327,7 @@ mod tests { #[actix_rt::test] async fn test_route() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test") @@ -356,35 +356,35 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&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; + let resp = call_service(&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).await; + let resp = call_service(&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).await; + let resp = call_service(&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).await; + let resp = call_service(&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).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/src/scope.rs b/src/scope.rs index 2da4f5546..d17acd843 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -155,10 +155,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.app_data.is_none() { - self.app_data = Some(Extensions::new()); - } - self.app_data.as_mut().unwrap().insert(data); + self.app_data + .get_or_insert_with(Extensions::new) + .insert(data); + self } @@ -200,18 +200,9 @@ where self.services.extend(cfg.services); self.external.extend(cfg.external); - if !cfg.data.is_empty() { - let mut data = self.app_data.unwrap_or_else(Extensions::new); - - for value in cfg.data.iter() { - value.create(&mut data); - } - - self.app_data = Some(data); - } self.app_data .get_or_insert_with(Extensions::new) - .extend(cfg.extensions); + .extend(cfg.app_data); self } @@ -389,7 +380,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Scope { @@ -432,11 +423,6 @@ where rmap.add(&mut rdef, None); } - // custom app data storage - if let Some(ref mut ext) = self.app_data { - config.set_service_data(ext); - } - // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { app_data: self.app_data.take().map(Rc::new), @@ -540,8 +526,8 @@ impl Service for ScopeService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -603,7 +589,7 @@ mod tests { #[actix_rt::test] async fn test_scope() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), )) .await; @@ -615,7 +601,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("").to(HttpResponse::Ok)) @@ -635,7 +621,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root2() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), )) .await; @@ -651,7 +637,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root3() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), )) .await; @@ -667,7 +653,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .route("/path1", web::get().to(HttpResponse::Ok)) @@ -695,7 +681,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route_without_leading_slash() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app").service( web::resource("path1") @@ -725,7 +711,7 @@ mod tests { #[actix_rt::test] async fn test_scope_guard() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .guard(guard::Get()) @@ -749,14 +735,13 @@ mod tests { #[actix_rt::test] async 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"])) - }), - ))) - .await; + let 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 = srv.call(req).await.unwrap(); @@ -777,7 +762,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -789,7 +774,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_no_slash() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -801,7 +786,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -823,7 +808,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_filter() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -849,7 +834,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project_id}").service(web::resource("/path1").to( |r: HttpRequest| { HttpResponse::Created() @@ -874,7 +859,7 @@ mod tests { #[actix_rt::test] async fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let 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!( @@ -906,7 +891,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("/path1").to(HttpResponse::Ok)) @@ -928,7 +913,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource_propagation() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::scope("/app1") @@ -956,7 +941,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap( @@ -973,7 +958,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -983,7 +968,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap_fn(|req, srv| { @@ -1003,7 +988,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -1013,7 +998,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).route( "/t", web::get().to(|data: web::Data| { @@ -1025,13 +1010,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_data_default_service() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).default_service(web::to( |data: web::Data| { assert_eq!(**data, 10); @@ -1042,13 +1027,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_app_data() { - let mut srv = init_service(App::new().app_data(web::Data::new(1usize)).service( + let srv = init_service(App::new().app_data(web::Data::new(1usize)).service( web::scope("app").app_data(web::Data::new(10usize)).route( "/t", web::get().to(|data: web::Data| { @@ -1060,17 +1045,16 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async 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)); - }))) - .await; + let 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 = srv.call(req).await.unwrap(); @@ -1079,13 +1063,12 @@ mod tests { #[actix_rt::test] async 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)); - })); - }))) - .await; + let 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 = srv.call(req).await.unwrap(); @@ -1094,24 +1077,20 @@ mod tests { #[actix_rt::test] async 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( - req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), - ) - }), - ); - })); - }))) - .await; + let 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( + req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), + ) + }), + ); + })); + }))) + .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1122,7 +1101,7 @@ mod tests { #[actix_rt::test] async fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( + let 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() @@ -1133,7 +1112,7 @@ mod tests { .await; let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!( diff --git a/src/server.rs b/src/server.rs index 8bfb27b77..59d589439 100644 --- a/src/server.rs +++ b/src/server.rs @@ -630,7 +630,7 @@ where /// } /// ``` pub fn run(self) -> Server { - self.builder.start() + self.builder.run() } } diff --git a/src/service.rs b/src/service.rs index 668b7d1b4..86949dca6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -231,8 +231,10 @@ impl ServiceRequest { self.payload = payload; } - #[doc(hidden)] - /// Add app data container to request's resolution set. + /// Add data container to request's resolution set. + /// + /// In middleware, prefer [`extensions_mut`](ServiceRequest::extensions_mut) for request-local + /// data since it is assumed that the same app data is presented for every request. pub fn add_data_container(&mut self, extensions: Rc) { Rc::get_mut(&mut (self.req).inner) .unwrap() @@ -540,7 +542,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").name("test").finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -550,7 +552,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").guard(guard::Get()).finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -565,7 +567,7 @@ mod tests { #[actix_rt::test] async fn test_service_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(42u32) .service(web::service("/test").name("test").finish( @@ -588,14 +590,14 @@ mod tests { fn test_fmt_debug() { let req = TestRequest::get() .uri("/index.html?test=1") - .header("x-test", "111") + .insert_header(("x-test", "111")) .to_srv_request(); let s = format!("{:?}", req); assert!(s.contains("ServiceRequest")); assert!(s.contains("test=1")); assert!(s.contains("x-test")); - let res = HttpResponse::Ok().header("x-test", "111").finish(); + let res = HttpResponse::Ok().insert_header(("x-test", "111")).finish(); let res = TestRequest::post() .uri("/index.html?test=1") .to_srv_response(res); diff --git a/src/test.rs b/src/test.rs index f8b789d1b..d51017b1e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,13 +1,13 @@ //! Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; + use std::net::SocketAddr; use std::rc::Rc; use std::sync::mpsc; use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; +use actix_http::http::header::{ContentType, IntoHeaderPair}; +use actix_http::http::{Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; @@ -60,7 +60,7 @@ pub fn default_service( /// /// #[actix_rt::test] /// async fn test_init_service() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) /// ).await; @@ -116,7 +116,7 @@ where /// /// #[actix_rt::test] /// async fn test_response() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { /// HttpResponse::Ok() @@ -127,11 +127,11 @@ where /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub async fn call_service(app: &mut S, req: R) -> S::Response +pub async fn call_service(app: &S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -147,7 +147,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -160,11 +160,11 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let result = test::read_response(&mut app, req).await; +/// let result = test::read_response(&app, req).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub async fn read_response(app: &mut S, req: Request) -> Bytes +pub async fn read_response(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -190,7 +190,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -203,7 +203,7 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// let result = test::read_body(resp).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } @@ -234,12 +234,12 @@ where /// /// #[actix_rt::test] /// async fn test_post_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { /// HttpResponse::Ok() -/// .json(person.into_inner())}) +/// .json(person)}) /// )) /// ).await; /// @@ -294,12 +294,12 @@ where /// /// #[actix_rt::test] /// async fn test_add_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { /// HttpResponse::Ok() -/// .json(person.into_inner())}) +/// .json(person)}) /// )) /// ).await; /// @@ -314,7 +314,7 @@ where /// let result: Person = test::read_response_json(&mut app, req).await; /// } /// ``` -pub async fn read_response_json(app: &mut S, req: Request) -> T +pub async fn read_response_json(app: &S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -349,7 +349,7 @@ where /// /// #[test] /// fn test_index() { -/// let req = test::TestRequest::with_header("content-type", "text/plain") +/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") /// .to_http_request(); /// /// let resp = index(req).await.unwrap(); @@ -389,21 +389,6 @@ impl TestRequest { TestRequest::default().uri(path) } - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr) - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value) - } - /// Create TestRequest and set method to `Method::GET` pub fn get() -> TestRequest { TestRequest::default().method(Method::GET) @@ -447,24 +432,25 @@ impl TestRequest { self } - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - self.req.set(hdr); - self - } - - /// Set a header - pub fn header(mut self, key: K, value: V) -> Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - self.req.header(key, value); + self.req.insert_header(header); self } - /// Set cookie for this request + /// Append a header, keeping any that were set with an equivalent field name. + pub fn append_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + self.req.append_header(header); + self + } + + /// Set cookie for this request. pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.req.cookie(cookie); self @@ -494,7 +480,7 @@ impl TestRequest { let bytes = serde_urlencoded::to_string(data) .expect("Failed to serialize test data as a urlencoded form"); self.req.set_payload(bytes); - self.req.set(ContentType::form_url_encoded()); + self.req.insert_header(ContentType::form_url_encoded()); self } @@ -504,7 +490,7 @@ impl TestRequest { let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); self.req.set_payload(bytes); - self.req.set(ContentType::json()); + self.req.insert_header(ContentType::json()); self } @@ -583,7 +569,7 @@ impl TestRequest { } /// Complete request creation, calls service and waits for response future completion. - pub async fn send_request(self, app: &mut S) -> S::Response + pub async fn send_request(self, app: &S) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -609,7 +595,7 @@ impl TestRequest { /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start( +/// let srv = test::start( /// || App::new().service( /// web::resource("/").to(my_handler)) /// ); @@ -649,7 +635,7 @@ where /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start_with(test::config().h1(), || +/// let srv = test::start_with(test::config().h1(), || /// App::new().service(web::resource("/").to(my_handler)) /// ); /// @@ -681,7 +667,7 @@ where // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + let sys = System::new(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); let factory = factory.clone(); @@ -774,7 +760,7 @@ where .unwrap(); sys.block_on(async { - let srv = srv.start(); + let srv = srv.run(); tx.send((System::current(), srv, local_addr)).unwrap(); }); @@ -1029,9 +1015,10 @@ mod tests { #[actix_rt::test] async fn test_basics() { - let req = TestRequest::with_hdr(header::ContentType::json()) + let req = TestRequest::default() .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header(header::ContentType::json()) + .insert_header(header::Date(SystemTime::now().into())) .param("test", "123") .data(10u32) .app_data(20u64) @@ -1056,7 +1043,7 @@ mod tests { #[actix_rt::test] async fn test_request_methods() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::put().to(|| HttpResponse::Ok().body("put!"))) @@ -1068,28 +1055,28 @@ mod tests { let put_req = TestRequest::put() .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, put_req).await; + let result = read_response(&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") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, patch_req).await; + let result = read_response(&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).await; + let result = read_response(&app, delete_req).await; assert_eq!(result, Bytes::from_static(b"delete!")); } #[actix_rt::test] async fn test_response() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1099,16 +1086,16 @@ mod tests { let req = TestRequest::post() .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, req).await; + let result = read_response(&app, req).await; assert_eq!(result, Bytes::from_static(b"welcome!")); } #[actix_rt::test] async fn test_send_request() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1118,7 +1105,7 @@ mod tests { let resp = TestRequest::get() .uri("/index.html") - .send_request(&mut app) + .send_request(&app) .await; let result = read_body(resp).await; @@ -1133,10 +1120,8 @@ mod tests { #[actix_rt::test] async 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()) - }), + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1144,20 +1129,18 @@ mod tests { let req = TestRequest::post() .uri("/people") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) .to_request(); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); } #[actix_rt::test] async fn test_body_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()) - }), + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1165,9 +1148,9 @@ mod tests { let resp = TestRequest::post() .uri("/people") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) - .send_request(&mut app) + .send_request(&app) .await; let result: Person = read_body_json(resp).await; @@ -1176,10 +1159,8 @@ mod tests { #[actix_rt::test] async 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()) - }), + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), ))) .await; @@ -1195,17 +1176,15 @@ mod tests { assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } #[actix_rt::test] async 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()) - }), + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1221,7 +1200,7 @@ mod tests { assert_eq!(req.content_type(), "application/json"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } @@ -1239,7 +1218,7 @@ mod tests { } } - let mut app = init_service( + let app = init_service( App::new().service(web::resource("/index.html").to(async_with_block)), ) .await; @@ -1256,7 +1235,7 @@ mod tests { HttpResponse::Ok() } - let mut app = init_service( + let app = init_service( App::new() .data(10usize) .service(web::resource("/index.html").to(handler)), @@ -1312,7 +1291,7 @@ mod tests { .data(addr.clone()) .service(web::resource("/").to(actor_handler)); - let mut app = init_service(srv).await; + let app = init_service(srv).await; let req = TestRequest::post().uri("/").to_request(); let res = app.call(req).await.unwrap(); diff --git a/src/types/form.rs b/src/types/form.rs index 71680b19a..96e09ee1c 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -381,11 +381,11 @@ mod tests { #[actix_rt::test] async 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(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); assert_eq!( @@ -414,25 +414,26 @@ mod tests { #[actix_rt::test] async 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 (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, "xxxx")) + .to_http_parts(); 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 (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_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") + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "text/plain")) + .insert_header((CONTENT_LENGTH, 10)) .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await; assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); @@ -440,11 +441,11 @@ mod tests { #[actix_rt::test] async 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(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); assert_eq!( @@ -455,13 +456,14 @@ mod tests { } ); - 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::default() + .insert_header(( + CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + )) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); assert_eq!( @@ -497,8 +499,8 @@ mod tests { let ctype = HeaderValue::from_static("application/x-www-form-urlencoded"); let (req, mut pl) = TestRequest::default() - .header(CONTENT_TYPE, ctype) - .header(CONTENT_LENGTH, HeaderValue::from_static("20")) + .insert_header((CONTENT_TYPE, ctype)) + .insert_header((CONTENT_LENGTH, HeaderValue::from_static("20"))) .set_payload(Bytes::from_static(b"hello=test&counter=4")) .app_data(web::Data::new(FormConfig::default().limit(10))) .to_http_parts(); diff --git a/src/types/json.rs b/src/types/json.rs index edfb775f3..41618b409 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -269,7 +269,7 @@ impl JsonConfig { } } -// Allow shared refs to default. +/// Allow shared refs used as default. const DEFAULT_CONFIG: JsonConfig = JsonConfig { limit: 32_768, // 2^15 bytes, (~32kB) err_handler: None, @@ -427,7 +427,7 @@ mod tests { use crate::{ error::InternalError, http::{ - header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + header::{self, CONTENT_LENGTH, CONTENT_TYPE}, StatusCode, }, test::{load_stream, TestRequest}, @@ -469,14 +469,14 @@ mod tests { #[actix_rt::test] async fn test_custom_error_responder() { let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .app_data(JsonConfig::default().limit(10).error_handler(|err, _| { let msg = MyObject { @@ -500,14 +500,14 @@ mod tests { #[actix_rt::test] async fn test_extract() { let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); @@ -521,14 +521,14 @@ mod tests { ); let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .app_data(JsonConfig::default().limit(10)) .to_http_parts(); @@ -538,14 +538,14 @@ mod tests { .contains("Json payload size is bigger than allowed")); let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .app_data( JsonConfig::default() @@ -564,23 +564,23 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() - .header( + .insert_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( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) + )) .to_http_parts(); let json = JsonBody::::new(&req, &mut pl, None) @@ -589,14 +589,14 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); @@ -611,17 +611,18 @@ mod tests { #[actix_rt::test] async 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\"}")) - .app_data(JsonConfig::default().limit(4096)) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().limit(4096)) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_err()) @@ -629,19 +630,20 @@ mod tests { #[actix_rt::test] async 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\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_ok()) @@ -649,19 +651,20 @@ mod tests { #[actix_rt::test] async 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\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_err()) @@ -670,8 +673,8 @@ mod tests { #[actix_rt::test] async fn test_with_config_in_data_wrapper() { let (req, mut pl) = TestRequest::default() - .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) - .header(CONTENT_LENGTH, HeaderValue::from_static("16")) + .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((CONTENT_LENGTH, 16)) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .app_data(web::Data::new(JsonConfig::default().limit(10))) .to_http_parts(); diff --git a/src/types/payload.rs b/src/types/payload.rs index 22528031c..724034522 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -191,6 +191,9 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result Self { Self { limit, @@ -206,7 +209,7 @@ impl PayloadConfig { } } - /// Set maximum accepted payload size. The default limit is 256kB. + /// Set maximum accepted payload size in bytes. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -247,7 +250,7 @@ impl PayloadConfig { } } -// Allow shared refs to default. +/// Allow shared refs used as defaults. const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { limit: DEFAULT_CONFIG_LIMIT, mimetype: None, @@ -286,10 +289,12 @@ impl HttpMessageBody { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { match l.to_str() { Ok(s) => match s.parse::() { - Ok(l) if l > DEFAULT_CONFIG_LIMIT => { - err = Some(PayloadError::Overflow) + Ok(l) => { + if l > DEFAULT_CONFIG_LIMIT { + err = Some(PayloadError::Overflow); + } + length = Some(l) } - Ok(l) => length = Some(l), Err(_) => err = Some(PayloadError::UnknownLength), }, Err(_) => err = Some(PayloadError::UnknownLength), @@ -313,9 +318,11 @@ impl HttpMessageBody { /// Change max size of payload. By default max size is 256kB pub fn limit(mut self, limit: usize) -> Self { if let Some(l) = self.length { - if l > limit { - self.err = Some(PayloadError::Overflow); - } + self.err = if l > limit { + Some(PayloadError::Overflow) + } else { + None + }; } self.limit = limit; self @@ -364,14 +371,13 @@ mod tests { let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .to_http_request(); + let req = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .to_http_request(); assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json") + let req = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/json")) .to_http_request(); assert!(cfg.check_mimetype(&req).is_ok()); } @@ -386,7 +392,7 @@ mod tests { "payload is probably json string" } - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/bytes-app-data") @@ -416,49 +422,50 @@ mod tests { .await; let req = TestRequest::with_uri("/bytes-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-app-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/bytes-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-app-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_bytes() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "11")) .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); @@ -468,7 +475,8 @@ mod tests { #[actix_rt::test] async fn test_string() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "11")) .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); @@ -478,7 +486,8 @@ mod tests { #[actix_rt::test] async fn test_message_body() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "xxxx")) .to_srv_request() .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; @@ -487,7 +496,8 @@ mod tests { _ => unreachable!("error"), } - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "1000000")) .to_srv_request() .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 5eca14931..78d4ef685 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -13,7 +13,7 @@ async fn test_start() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); sys.block_on(async { let srv = HttpServer::new(|| { @@ -91,7 +91,7 @@ async fn test_start_ssl() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); let builder = ssl_acceptor().unwrap(); let srv = HttpServer::new(|| { diff --git a/tests/test_server.rs b/tests/test_server.rs index 43ee1230d..0f51a2fdb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -108,7 +108,7 @@ async fn test_body_gzip() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -137,7 +137,7 @@ async fn test_body_gzip2() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -178,7 +178,7 @@ async fn test_body_encoding_override() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .send() .await .unwrap(); @@ -197,7 +197,7 @@ async fn test_body_encoding_override() { let mut response = srv .request(actix_web::http::Method::GET, srv.url("/raw")) .no_decompress() - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .send() .await .unwrap(); @@ -231,7 +231,7 @@ async fn test_body_gzip_large() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -269,7 +269,7 @@ async fn test_body_gzip_large_random() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -300,7 +300,7 @@ async fn test_body_chunked_implicit() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -333,7 +333,7 @@ async fn test_body_br_streaming() { let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "br") + .append_header((ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -406,7 +406,7 @@ async fn test_body_deflate() { // client request let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .no_decompress() .send() .await @@ -433,7 +433,7 @@ async fn test_body_brotli() { // client request let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "br") + .append_header((ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -466,7 +466,7 @@ async fn test_encoding() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .insert_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -492,7 +492,7 @@ async fn test_gzip_encoding() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -519,7 +519,7 @@ async fn test_gzip_encoding_large() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -551,7 +551,7 @@ async fn test_reading_gzip_encoding_large_random() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -578,7 +578,7 @@ async fn test_reading_deflate_encoding() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -605,7 +605,7 @@ async fn test_reading_deflate_encoding_large() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -637,7 +637,7 @@ async fn test_reading_deflate_encoding_large_random() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -664,7 +664,7 @@ async fn test_brotli_encoding() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "br") + .append_header((CONTENT_ENCODING, "br")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -699,7 +699,7 @@ async fn test_brotli_encoding_large() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "br") + .append_header((CONTENT_ENCODING, "br")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -739,7 +739,7 @@ async fn test_brotli_encoding_large_openssl() { // client request let mut response = srv .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "br") + .append_header((actix_web::http::header::CONTENT_ENCODING, "br")) .send_body(enc) .await .unwrap(); @@ -788,7 +788,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() { // client request let req = srv .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "deflate") + .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) .send_stream(TestBody::new(Bytes::from(enc), 1024)); let mut response = req.await.unwrap(); @@ -800,61 +800,58 @@ async fn test_reading_deflate_encoding_large_random_rustls() { assert_eq!(bytes, Bytes::from(data)); } -// #[test] -// fn test_server_cookies() { -// use actix_web::http; +#[actix_rt::test] +async fn test_server_cookies() { + use actix_web::{http, HttpMessage}; -// let srv = test::TestServer::with_factory(|| { -// App::new().resource("/", |r| { -// r.f(|_| { -// HttpResponse::Ok() -// .cookie( -// http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(), -// ) -// .cookie(http::Cookie::new("second", "first_value")) -// .cookie(http::Cookie::new("second", "second_value")) -// .finish() -// }) -// }) -// }); + let srv = test::start(|| { + App::new().default_service(web::to(|| { + HttpResponse::Ok() + .cookie( + http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(), + ) + .cookie(http::Cookie::new("second", "first_value")) + .cookie(http::Cookie::new("second", "second_value")) + .finish() + })) + }); -// let first_cookie = http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(); -// let second_cookie = http::Cookie::new("second", "second_value"); + let first_cookie = http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = http::Cookie::new("second", "second_value"); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.get("/"); + let res = req.send().await.unwrap(); + assert!(res.status().is_success()); -// let cookies = response.cookies().expect("To have cookies"); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } + let cookies = res.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } -// let first_cookie = first_cookie.to_string(); -// let second_cookie = second_cookie.to_string(); -// //Check that we have exactly two instances of raw cookie headers -// let cookies = response -// .headers() -// .get_all(http::header::SET_COOKIE) -// .iter() -// .map(|header| header.to_str().expect("To str").to_string()) -// .collect::>(); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } -// } + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + // Check that we have exactly two instances of raw cookie headers + let cookies = res + .headers() + .get_all(http::header::SET_COOKIE) + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } +} #[actix_rt::test] async fn test_slow_request() { @@ -889,28 +886,3 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); } - -// #[cfg(feature = "openssl")] -// #[actix_rt::test] -// async fn test_ssl_handshake_timeout() { -// use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -// use std::net; - -// // load ssl keys -// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); -// builder -// .set_private_key_file("tests/key.pem", SslFiletype::PEM) -// .unwrap(); -// builder -// .set_certificate_chain_file("tests/cert.pem") -// .unwrap(); - -// let srv = test::start_with(test::config().openssl(builder.build()), || { -// App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) -// }); - -// let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); -// let mut data = String::new(); -// let _ = stream.read_to_string(&mut data); -// assert!(data.is_empty()); -// } diff --git a/tests/test_weird_poll.rs b/tests/test_weird_poll.rs index 7e4300901..5844ea2c2 100644 --- a/tests/test_weird_poll.rs +++ b/tests/test_weird_poll.rs @@ -1,30 +1,30 @@ -// Regression test for #/1321 +//! Regression test for https://github.com/actix/actix-web/issues/1321 -/* -use futures::task::{noop_waker, Context}; -use futures::stream::once; -use actix_http::body::{MessageBody, BodyStream}; -use bytes::Bytes; +// use actix_http::body::{BodyStream, MessageBody}; +// use bytes::Bytes; +// use futures_channel::oneshot; +// use futures_util::{ +// stream::once, +// task::{noop_waker, Context}, +// }; -Disable weird poll until actix-web is based on actix-http 2.0.0 +// #[test] +// fn weird_poll() { +// let (sender, receiver) = oneshot::channel(); +// let mut body_stream = Ok(BodyStream::new(once(async { +// let x = Box::new(0); +// let y = &x; +// receiver.await.unwrap(); +// let _z = **y; +// Ok::<_, ()>(Bytes::new()) +// }))); -#[test] -fn weird_poll() { - let (sender, receiver) = futures::channel::oneshot::channel(); - let mut body_stream = Ok(BodyStream::new(once(async { - let x = Box::new(0); - let y = &x; - receiver.await.unwrap(); - let _z = **y; - Ok::<_, ()>(Bytes::new()) - }))); +// let waker = noop_waker(); +// let mut cx = Context::from_waker(&waker); - let waker = noop_waker(); - let mut context = Context::from_waker(&waker); - - let _ = body_stream.as_mut().unwrap().poll_next(&mut context); - sender.send(()).unwrap(); - let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context); -} - -*/ +// let _ = body_stream.as_mut().unwrap().poll_next(&mut cx); +// sender.send(()).unwrap(); +// let _ = std::mem::replace(&mut body_stream, Err([0; 32])) +// .unwrap() +// .poll_next(&mut cx); +// }