From 136dac135245daec70b6b68a116dca9132b3680f Mon Sep 17 00:00:00 2001 From: James Wright Date: Thu, 3 Jun 2021 03:28:09 +0100 Subject: [PATCH 001/861] Additional test coverage and tidyup (middleware::normalize) (#2243) --- src/middleware/normalize.rs | 216 ++++++++++++++++++++++++------------ 1 file changed, 148 insertions(+), 68 deletions(-) diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ec6c2a344..cbed78714 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -189,6 +189,7 @@ mod tests { use super::*; use crate::{ dev::ServiceRequest, + guard::fn_guard, test::{call_service, init_service, TestRequest}, web, App, HttpResponse, }; @@ -199,37 +200,34 @@ mod tests { App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) - .service(web::resource("/v1/something").to(HttpResponse::Ok)), + .service(web::resource("/v1/something").to(HttpResponse::Ok)) + .service( + web::resource("/v2/something") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), ) .await; - let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); + let test_uris = vec![ + "/", + "/?query=test", + "///", + "/v1//something", + "/v1//something////", + "//v1/something", + "//v1//////something", + "/v2//something?query=test", + "/v2//something////?query=test", + "//v2/something?query=test", + "//v2//////something?query=test", + ]; - let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); - - let req = TestRequest::with_uri("///").to_request(); - 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(&app, req).await; - assert!(res.status().is_success()); - - let req2 = TestRequest::with_uri("//v1/something").to_request(); - 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(&app, req3).await; - assert!(res3.status().is_success()); - - let req4 = TestRequest::with_uri("/v1//something").to_request(); - let res4 = call_service(&app, req4).await; - assert!(res4.status().is_success()); + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_request(); + let res = call_service(&app, req).await; + assert!(res.status().is_success(), "Failed uri: {}", uri); + } } #[actix_rt::test] @@ -238,38 +236,114 @@ mod tests { App::new() .wrap(NormalizePath(TrailingSlash::Trim)) .service(web::resource("/").to(HttpResponse::Ok)) - .service(web::resource("/v1/something").to(HttpResponse::Ok)), + .service(web::resource("/v1/something").to(HttpResponse::Ok)) + .service( + web::resource("/v2/something") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), ) .await; - // root paths should still work - let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); + let test_uris = vec![ + "/", + "///", + "/v1/something", + "/v1/something/", + "/v1/something////", + "//v1//something", + "//v1//something//", + "/v2/something?query=test", + "/v2/something/?query=test", + "/v2/something////?query=test", + "//v2//something?query=test", + "//v2//something//?query=test", + ]; - let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_request(); + let res = call_service(&app, req).await; + assert!(res.status().is_success(), "Failed uri: {}", uri); + } + } - let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); + #[actix_rt::test] + async fn trim_root_trailing_slashes_with_query() { + let app = init_service( + App::new().wrap(NormalizePath(TrailingSlash::Trim)).service( + web::resource("/") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), + ) + .await; - let req = TestRequest::with_uri("/v1/something////").to_request(); - let res = call_service(&app, req).await; - assert!(res.status().is_success()); + let test_uris = vec!["/?query=test", "//?query=test", "///?query=test"]; - let req2 = TestRequest::with_uri("/v1/something/").to_request(); - let res2 = call_service(&app, req2).await; - assert!(res2.status().is_success()); + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_request(); + let res = call_service(&app, req).await; + assert!(res.status().is_success(), "Failed uri: {}", uri); + } + } - let req3 = TestRequest::with_uri("//v1//something//").to_request(); - let res3 = call_service(&app, req3).await; - assert!(res3.status().is_success()); + #[actix_rt::test] + async fn ensure_trailing_slash() { + let app = init_service( + App::new() + .wrap(NormalizePath(TrailingSlash::Always)) + .service(web::resource("/").to(HttpResponse::Ok)) + .service(web::resource("/v1/something/").to(HttpResponse::Ok)) + .service( + web::resource("/v2/something/") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), + ) + .await; - let req4 = TestRequest::with_uri("//v1//something").to_request(); - let res4 = call_service(&app, req4).await; - assert!(res4.status().is_success()); + let test_uris = vec![ + "/", + "///", + "/v1/something", + "/v1/something/", + "/v1/something////", + "//v1//something", + "//v1//something//", + "/v2/something?query=test", + "/v2/something/?query=test", + "/v2/something////?query=test", + "//v2//something?query=test", + "//v2//something//?query=test", + ]; + + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_request(); + let res = call_service(&app, req).await; + assert!(res.status().is_success(), "Failed uri: {}", uri); + } + } + + #[actix_rt::test] + async fn ensure_root_trailing_slash_with_query() { + let app = init_service( + App::new() + .wrap(NormalizePath(TrailingSlash::Always)) + .service( + web::resource("/") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), + ) + .await; + + let test_uris = vec!["/?query=test", "//?query=test", "///?query=test"]; + + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_request(); + let res = call_service(&app, req).await; + assert!(res.status().is_success(), "Failed uri: {}", uri); + } } #[actix_rt::test] @@ -279,7 +353,12 @@ mod tests { .wrap(NormalizePath(TrailingSlash::MergeOnly)) .service(web::resource("/").to(HttpResponse::Ok)) .service(web::resource("/v1/something").to(HttpResponse::Ok)) - .service(web::resource("/v1/").to(HttpResponse::Ok)), + .service(web::resource("/v1/").to(HttpResponse::Ok)) + .service( + web::resource("/v2/something") + .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .to(HttpResponse::Ok), + ), ) .await; @@ -295,12 +374,16 @@ mod tests { ("/v1////", true), ("//v1//", true), ("///v1", false), + ("/v2/something?query=test", true), + ("/v2/something/?query=test", false), + ("/v2/something//?query=test", false), + ("//v2//something?query=test", true), ]; - for (path, success) in tests { - let req = TestRequest::with_uri(path).to_request(); + for (uri, success) in tests { + let req = TestRequest::with_uri(uri).to_request(); let res = call_service(&app, req).await; - assert_eq!(res.status().is_success(), success); + assert_eq!(res.status().is_success(), success, "Failed uri: {}", uri); } } @@ -316,21 +399,18 @@ mod tests { .await .unwrap(); - let req = TestRequest::with_uri("/v1//something////").to_srv_request(); - let res = normalize.call(req).await.unwrap(); - assert!(res.status().is_success()); + let test_uris = vec![ + "/v1//something////", + "///v1/something", + "//v1///something", + "/v1//something", + ]; - let req2 = TestRequest::with_uri("///v1/something").to_srv_request(); - let res2 = normalize.call(req2).await.unwrap(); - assert!(res2.status().is_success()); - - let req3 = TestRequest::with_uri("//v1///something").to_srv_request(); - let res3 = normalize.call(req3).await.unwrap(); - assert!(res3.status().is_success()); - - let req4 = TestRequest::with_uri("/v1//something").to_srv_request(); - let res4 = normalize.call(req4).await.unwrap(); - assert!(res4.status().is_success()); + for uri in test_uris { + let req = TestRequest::with_uri(uri).to_srv_request(); + let res = normalize.call(req).await.unwrap(); + assert!(res.status().is_success(), "Failed uri: {}", uri); + } } #[actix_rt::test] From 34792934168897d0559cadb31720ad27a5114a34 Mon Sep 17 00:00:00 2001 From: Arthur Le Moigne Date: Thu, 3 Jun 2021 22:32:52 +0200 Subject: [PATCH 002/861] Add zstd ContentEncoding support (#2244) Co-authored-by: Igor Aleksanov Co-authored-by: Rob Ede --- Cargo.toml | 1 + actix-http/CHANGES.md | 3 + actix-http/Cargo.toml | 3 +- actix-http/src/encoding/decoder.rs | 36 ++++++ actix-http/src/encoding/encoder.rs | 20 +++ .../src/header/shared/content_encoding.rs | 7 + tests/test_server.rs | 120 ++++++++++++++++++ 7 files changed, 189 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5aa302333..6893067d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ brotli2 = "0.3.2" criterion = "0.3" env_logger = "0.8" flate2 = "1.0.13" +zstd = "0.7" rand = "0.8" rcgen = "0.8" serde_derive = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ec7d8ee3b..fbf9f8e99 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,6 +9,7 @@ * Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] * `Response::into_body` that consumes response and returns body type. [#2201] * `impl Default` for `Response`. [#2201] +* Add zstd support for `ContentEncoding`. [#2244] ### Changed * The `MessageBody` trait now has an associated `Error` type. [#2183] @@ -35,6 +36,8 @@ [#2201]: https://github.com/actix/actix-web/pull/2201 [#2205]: https://github.com/actix/actix-web/pull/2205 [#2215]: https://github.com/actix/actix-web/pull/2215 +[#2244]: https://github.com/actix/actix-web/pull/2244 + ## 3.0.0-beta.6 - 2021-04-17 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1f7df39a6..809be868d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -32,7 +32,7 @@ openssl = ["actix-tls/openssl"] rustls = ["actix-tls/rustls"] # enable compression support -compress = ["flate2", "brotli2"] +compress = ["flate2", "brotli2", "zstd"] # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] @@ -76,6 +76,7 @@ tokio = { version = "1.2", features = ["sync"] } # compression brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } +zstd = { version = "0.7", optional = true } trust-dns-resolver = { version = "0.20.0", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index f0abae865..58981e82e 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -12,6 +12,7 @@ use brotli2::write::BrotliDecoder; use bytes::Bytes; use flate2::write::{GzDecoder, ZlibDecoder}; use futures_core::{ready, Stream}; +use zstd::stream::write::Decoder as ZstdDecoder; use crate::{ encoding::Writer, @@ -45,6 +46,12 @@ where ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( GzDecoder::new(Writer::new()), ))), + ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new( + ZstdDecoder::new(Writer::new()).expect( + "Failed to create zstd decoder. This is a bug. \ + Please report it to the actix-web repository.", + ), + ))), _ => None, }; @@ -144,6 +151,9 @@ enum ContentDecoder { Deflate(Box>), Gzip(Box>), Br(Box>), + // We need explicit 'static lifetime here because ZstdDecoder need lifetime + // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` + Zstd(Box>), } impl ContentDecoder { @@ -186,6 +196,18 @@ impl ContentDecoder { } Err(e) => Err(e), }, + + ContentDecoder::Zstd(ref mut decoder) => match decoder.flush() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, } } @@ -232,6 +254,20 @@ impl ContentDecoder { } Err(e) => Err(e), }, + + ContentDecoder::Zstd(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, } } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index b8bc8b68d..6adde9be2 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -15,6 +15,7 @@ use derive_more::Display; use flate2::write::{GzEncoder, ZlibEncoder}; use futures_core::ready; use pin_project::pin_project; +use zstd::stream::write::Encoder as ZstdEncoder; use crate::{ body::{Body, BodySize, BoxAnyBody, MessageBody, ResponseBody}, @@ -237,6 +238,9 @@ enum ContentEncoder { Deflate(ZlibEncoder), Gzip(GzEncoder), Br(BrotliEncoder), + // We need explicit 'static lifetime here because ZstdEncoder need lifetime + // argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static` + Zstd(ZstdEncoder<'static, Writer>), } impl ContentEncoder { @@ -253,6 +257,10 @@ impl ContentEncoder { ContentEncoding::Br => { Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) } + ContentEncoding::Zstd => { + let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?; + Some(ContentEncoder::Zstd(encoder)) + } _ => None, } } @@ -263,6 +271,7 @@ impl ContentEncoder { ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(), } } @@ -280,6 +289,10 @@ impl ContentEncoder { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + ContentEncoder::Zstd(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, } } @@ -306,6 +319,13 @@ impl ContentEncoder { Err(err) } }, + ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(()), + Err(err) => { + trace!("Error decoding ztsd encoding: {}", err); + Err(err) + } + }, } } } diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index b93d66101..b9c1d2795 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -23,6 +23,9 @@ pub enum ContentEncoding { /// Gzip algorithm. Gzip, + // Zstd algorithm. + Zstd, + /// Indicates the identity function (i.e. no compression, nor modification). Identity, } @@ -41,6 +44,7 @@ impl ContentEncoding { ContentEncoding::Br => "br", ContentEncoding::Gzip => "gzip", ContentEncoding::Deflate => "deflate", + ContentEncoding::Zstd => "zstd", ContentEncoding::Identity | ContentEncoding::Auto => "identity", } } @@ -53,6 +57,7 @@ impl ContentEncoding { ContentEncoding::Gzip => 1.0, ContentEncoding::Deflate => 0.9, ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + ContentEncoding::Zstd => 0.0, } } } @@ -81,6 +86,8 @@ impl From<&str> for ContentEncoding { ContentEncoding::Gzip } else if val.eq_ignore_ascii_case("deflate") { ContentEncoding::Deflate + } else if val.eq_ignore_ascii_case("zstd") { + ContentEncoding::Zstd } else { ContentEncoding::default() } diff --git a/tests/test_server.rs b/tests/test_server.rs index c341aa0ce..520eb5ce2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -29,6 +29,7 @@ use openssl::{ x509::X509, }; use rand::{distributions::Alphanumeric, Rng}; +use zstd::stream::{read::Decoder as ZstdDecoder, write::Encoder as ZstdEncoder}; use actix_web::dev::BodyEncoding; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; @@ -476,6 +477,125 @@ async fn test_body_brotli() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[actix_rt::test] +async fn test_body_zstd() { + let srv = actix_test::start_with(actix_test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Zstd)) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) + }); + + // client request + let mut response = srv + .get("/") + .append_header((ACCEPT_ENCODING, "zstd")) + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + + // decode + let mut e = ZstdDecoder::new(&bytes[..]).unwrap(); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_body_zstd_streaming() { + let srv = actix_test::start_with(actix_test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Zstd)) + .service(web::resource("/").route(web::to(move || { + HttpResponse::Ok() + .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) + }))) + }); + + // client request + let mut response = srv + .get("/") + .append_header((ACCEPT_ENCODING, "zstd")) + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + + // decode + let mut e = ZstdDecoder::new(&bytes[..]).unwrap(); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_zstd_encoding() { + let srv = actix_test::start_with(actix_test::config().h1(), || { + App::new().service( + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) + }); + + let mut e = ZstdEncoder::new(Vec::new(), 5).unwrap(); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv + .post("/") + .append_header((CONTENT_ENCODING, "zstd")) + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_zstd_encoding_large() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(320_000) + .map(char::from) + .collect::(); + + let srv = actix_test::start_with(actix_test::config().h1(), || { + App::new().service( + web::resource("/") + .app_data(web::PayloadConfig::new(320_000)) + .route(web::to(move |body: Bytes| { + HttpResponse::Ok().streaming(TestBody::new(body, 10240)) + })), + ) + }); + + let mut e = ZstdEncoder::new(Vec::new(), 5).unwrap(); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv + .post("/") + .append_header((CONTENT_ENCODING, "zstd")) + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().limit(320_000).await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + #[actix_rt::test] async fn test_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { From 0bb035cfa79816d4ead678fc23e2839b3120c7e1 Mon Sep 17 00:00:00 2001 From: Yerkebulan Tulibergenov Date: Thu, 3 Jun 2021 18:54:40 -0700 Subject: [PATCH 003/861] Add information about Actix discord server (#2247) --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 96e6ecbf8..0d488adf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ //! //! * [Website & User Guide](https://actix.rs/) //! * [Examples Repository](https://github.com/actix/examples) +//! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x) //! * [Community Chat on Gitter](https://gitter.im/actix/actix-web) //! //! To get started navigating the API docs, you may consider looking at the following pages first: From b1e841f1686c9eb681155cdeff4fadda14c0b5ce Mon Sep 17 00:00:00 2001 From: Thales <46510852+thalesfragoso@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:19:45 -0300 Subject: [PATCH 004/861] Don't normalize URIs with no valid path (#2246) --- CHANGES.md | 2 + src/middleware/normalize.rs | 96 ++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 162f9f61b..8553ca82d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,12 +10,14 @@ * Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] * `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] ### Removed * `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 +[#2246]: https://github.com/actix/actix-web/pull/2246 ## 4.0.0-beta.6 - 2021-04-17 diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index cbed78714..219af1c6a 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -137,53 +137,57 @@ where let original_path = head.uri.path(); - // Either adds a string to the end (duplicates will be removed anyways) or trims all slashes from the end - let path = match self.trailing_slash_behavior { - TrailingSlash::Always => original_path.to_string() + "/", - TrailingSlash::MergeOnly => original_path.to_string(), - TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(), - }; - - // normalize multiple /'s to one / - let path = self.merge_slash.replace_all(&path, "/"); - - // Ensure root paths are still resolvable. If resulting path is blank after previous step - // it means the path was one or more slashes. Reduce to single slash. - let path = if path.is_empty() { "/" } else { path.as_ref() }; - - // Check whether the path has been changed - // - // This check was previously implemented as string length comparison - // - // That approach fails when a trailing slash is added, - // and a duplicate slash is removed, - // since the length of the strings remains the same - // - // For example, the path "/v1//s" will be normalized to "/v1/s/" - // Both of the paths have the same length, - // so the change can not be deduced from the length comparison - if path != original_path { - let mut parts = head.uri.clone().into_parts(); - let query = parts.path_and_query.as_ref().and_then(|pq| pq.query()); - - let path = if let Some(q) = query { - Bytes::from(format!("{}?{}", path, q)) - } else { - Bytes::copy_from_slice(path.as_bytes()) + // An empty path here means that the URI has no valid path. We skip normalization in this + // case, because adding a path can make the URI invalid + if !original_path.is_empty() { + // Either adds a string to the end (duplicates will be removed anyways) or trims all + // slashes from the end + let path = match self.trailing_slash_behavior { + TrailingSlash::Always => format!("{}/", original_path), + TrailingSlash::MergeOnly => original_path.to_string(), + TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(), }; - parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap()); - let uri = Uri::from_parts(parts).unwrap(); - req.match_info_mut().get_mut().update(&uri); - req.head_mut().uri = uri; + // normalize multiple /'s to one / + let path = self.merge_slash.replace_all(&path, "/"); + + // Ensure root paths are still resolvable. If resulting path is blank after previous + // step it means the path was one or more slashes. Reduce to single slash. + let path = if path.is_empty() { "/" } else { path.as_ref() }; + + // Check whether the path has been changed + // + // This check was previously implemented as string length comparison + // + // That approach fails when a trailing slash is added, + // and a duplicate slash is removed, + // since the length of the strings remains the same + // + // For example, the path "/v1//s" will be normalized to "/v1/s/" + // Both of the paths have the same length, + // so the change can not be deduced from the length comparison + if path != original_path { + let mut parts = head.uri.clone().into_parts(); + let query = parts.path_and_query.as_ref().and_then(|pq| pq.query()); + + let path = match query { + Some(q) => Bytes::from(format!("{}?{}", path, q)), + None => Bytes::copy_from_slice(path.as_bytes()), + }; + parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap()); + + let uri = Uri::from_parts(parts).unwrap(); + req.match_info_mut().get_mut().update(&uri); + req.head_mut().uri = uri; + } } - self.service.call(req) } } #[cfg(test)] mod tests { + use actix_http::StatusCode; use actix_service::IntoService; use super::*; @@ -387,6 +391,22 @@ mod tests { } } + #[actix_rt::test] + async fn no_path() { + let app = init_service( + App::new() + .wrap(NormalizePath::default()) + .service(web::resource("/").to(HttpResponse::Ok)), + ) + .await; + + // This URI will be interpreted as an authority form, i.e. there is no path nor scheme + // (https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3) + let req = TestRequest::with_uri("eh").to_request(); + let res = call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + #[actix_rt::test] async fn test_in_place_normalization() { let srv = |req: ServiceRequest| { From 2e1d76185487c323ed1b73bd2120693dc17e995e Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 8 Jun 2021 07:57:19 -0400 Subject: [PATCH 005/861] add Seal argument to sealed AsHeaderName methods (#2252) --- actix-http/src/header/as_name.rs | 14 ++++++++------ actix-http/src/header/map.rs | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index af81ff7f2..5ce321566 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -8,40 +8,42 @@ use http::header::{HeaderName, InvalidHeaderName}; pub trait AsHeaderName: Sealed {} +pub struct Seal; + pub trait Sealed { - fn try_as_name(&self) -> Result, InvalidHeaderName>; + fn try_as_name(&self, seal: Seal) -> Result, InvalidHeaderName>; } impl Sealed for HeaderName { - fn try_as_name(&self) -> Result, InvalidHeaderName> { + fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { Ok(Cow::Borrowed(self)) } } impl AsHeaderName for HeaderName {} impl Sealed for &HeaderName { - fn try_as_name(&self) -> Result, InvalidHeaderName> { + fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { Ok(Cow::Borrowed(*self)) } } impl AsHeaderName for &HeaderName {} impl Sealed for &str { - fn try_as_name(&self) -> Result, InvalidHeaderName> { + fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } } impl AsHeaderName for &str {} impl Sealed for String { - fn try_as_name(&self) -> Result, InvalidHeaderName> { + fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } } impl AsHeaderName for String {} impl Sealed for &String { - fn try_as_name(&self) -> Result, InvalidHeaderName> { + fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } } diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 106e44edb..be33ec02a 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -213,7 +213,7 @@ impl HeaderMap { } fn get_value(&self, key: impl AsHeaderName) -> Option<&Value> { - match key.try_as_name().ok()? { + match key.try_as_name(super::as_name::Seal).ok()? { Cow::Borrowed(name) => self.inner.get(name), Cow::Owned(name) => self.inner.get(&name), } @@ -279,7 +279,7 @@ impl HeaderMap { /// assert!(map.get("INVALID HEADER NAME").is_none()); /// ``` pub fn get_mut(&mut self, key: impl AsHeaderName) -> Option<&mut HeaderValue> { - match key.try_as_name().ok()? { + match key.try_as_name(super::as_name::Seal).ok()? { Cow::Borrowed(name) => self.inner.get_mut(name).map(|v| v.first_mut()), Cow::Owned(name) => self.inner.get_mut(&name).map(|v| v.first_mut()), } @@ -327,7 +327,7 @@ impl HeaderMap { /// assert!(map.contains_key(header::ACCEPT)); /// ``` pub fn contains_key(&self, key: impl AsHeaderName) -> bool { - match key.try_as_name() { + match key.try_as_name(super::as_name::Seal) { Ok(Cow::Borrowed(name)) => self.inner.contains_key(name), Ok(Cow::Owned(name)) => self.inner.contains_key(&name), Err(_) => false, @@ -410,7 +410,7 @@ impl HeaderMap { /// /// assert!(map.is_empty()); pub fn remove(&mut self, key: impl AsHeaderName) -> Removed { - let value = match key.try_as_name() { + let value = match key.try_as_name(super::as_name::Seal) { Ok(Cow::Borrowed(name)) => self.inner.remove(name), Ok(Cow::Owned(name)) => self.inner.remove(&name), Err(_) => None, From e46cda52280007cc459b3856a5df87345e9e5b93 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 8 Jun 2021 17:44:56 -0400 Subject: [PATCH 006/861] Deduplicate rt::main macro logic (#2255) --- Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 23 +++-------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6893067d5..bd4cdd91f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [dependencies] actix-codec = "0.4.0" -actix-macros = "0.2.0" +actix-macros = "0.2.1" actix-router = "0.2.7" actix-rt = "2.2" actix-server = "2.0.0-beta.3" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 336345014..2237f422c 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -171,27 +171,10 @@ method_macro! { #[proc_macro_attribute] pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { use quote::quote; - - let mut input = syn::parse_macro_input!(item as syn::ItemFn); - let attrs = &input.attrs; - let vis = &input.vis; - let sig = &mut input.sig; - let body = &input.block; - - if sig.asyncness.is_none() { - return syn::Error::new_spanned(sig.fn_token, "only async fn is supported") - .to_compile_error() - .into(); - } - - sig.asyncness = None; - + let input = syn::parse_macro_input!(item as syn::ItemFn); (quote! { - #(#attrs)* - #vis #sig { - actix_web::rt::System::new() - .block_on(async move { #body }) - } + #[actix_web::rt::main(system = "::actix_web::rt::System")] + #input }) .into() } From 812269d6568a8ed9ed22deaaa0489795c933f511 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Thu, 10 Jun 2021 17:38:35 +0300 Subject: [PATCH 007/861] clarify docs for BodyEncoding::encoding() (#2258) --- actix-files/src/named.rs | 2 ++ src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 519234f0d..2183eab5f 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -235,6 +235,8 @@ impl NamedFile { } /// Set content encoding for serving this file + /// + /// Must be used with [`actix_web::middleware::Compress`] to take effect. #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); diff --git a/src/lib.rs b/src/lib.rs index 0d488adf8..4e8093a2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,8 @@ pub mod dev { fn get_encoding(&self) -> Option; /// Set content encoding + /// + /// Must be used with [`crate::middleware::Compress`] to take effect. fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; } From 75f65fea4f27d7f15e10838b5ba6351350a3b0dc Mon Sep 17 00:00:00 2001 From: Victor Pirat <1115716+01101101@users.noreply.github.com> Date: Thu, 10 Jun 2021 17:25:21 +0200 Subject: [PATCH 008/861] Extends Rustls ALPN protocols instead of replacing them when creating Rustls based services (#2226) --- CHANGES.md | 1 + actix-http/CHANGES.md | 1 + actix-http/Cargo.toml | 1 + actix-http/src/h2/service.rs | 3 +- actix-http/src/service.rs | 3 +- actix-http/tests/test_rustls.rs | 110 +++++++++++++++++++++++++++++++- src/server.rs | 4 +- 7 files changed, 117 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8553ca82d..4a1742c95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ * Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] * `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] * `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] ### Removed diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index fbf9f8e99..99953ff26 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -19,6 +19,7 @@ * Update `language-tags` to `0.3`. * Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] * `ResponseBuilder::message_body` now returns a `Result`. [#2201] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 809be868d..1a0a32599 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -91,6 +91,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } +webpki = { version = "0.21.0" } [[example]] name = "ws" diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index a75abef7d..3a6d535d9 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -171,7 +171,8 @@ mod rustls { Error = TlsError, InitError = S::InitError, > { - let protos = vec!["h2".to_string().into()]; + let mut protos = vec![b"h2".to_vec()]; + protos.extend_from_slice(&config.alpn_protocols); config.set_protocols(&protos); Acceptor::new(config) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index d25a67a19..1c81e7568 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -305,7 +305,8 @@ mod rustls { Error = TlsError, InitError = (), > { - let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; + let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + protos.extend_from_slice(&config.alpn_protocols); config.set_protocols(&protos); Acceptor::new(config) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 2382d1ad3..eec417541 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -20,10 +20,15 @@ use futures_core::Stream; use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig as RustlsServerConfig, + NoClientAuth, ServerConfig as RustlsServerConfig, Session, }; +use webpki::DNSNameRef; -use std::io::{self, BufReader}; +use std::{ + io::{self, BufReader, Write}, + net::{SocketAddr, TcpStream as StdTcpStream}, + sync::Arc, +}; async fn load_body(mut stream: S) -> Result where @@ -52,6 +57,25 @@ fn tls_config() -> RustlsServerConfig { config } +pub fn get_negotiated_alpn_protocol( + addr: SocketAddr, + client_alpn_protocol: &[u8], +) -> Option> { + let mut config = rustls::ClientConfig::new(); + config.alpn_protocols.push(client_alpn_protocol.to_vec()); + let mut sess = rustls::ClientSession::new( + &Arc::new(config), + DNSNameRef::try_from_ascii_str("localhost").unwrap(), + ); + let mut sock = StdTcpStream::connect(addr).unwrap(); + let mut stream = rustls::Stream::new(&mut sess, &mut sock); + // The handshake will fails because the client will not be able to verify the server + // certificate, but it doesn't matter here as we are just interested in the negotiated ALPN + // protocol + let _ = stream.flush(); + sess.get_alpn_protocol().map(|proto| proto.to_vec()) +} + #[actix_rt::test] async fn test_h1() -> io::Result<()> { let srv = test_server(move || { @@ -460,3 +484,85 @@ async fn test_h1_service_error() { let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); } + +const H2_ALPN_PROTOCOL: &[u8] = b"h2"; +const HTTP1_1_ALPN_PROTOCOL: &[u8] = b"http/1.1"; +const CUSTOM_ALPN_PROTOCOL: &[u8] = b"custom"; + +#[actix_rt::test] +async fn test_alpn_h1() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .h1(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} + +#[actix_rt::test] +async fn test_alpn_h2() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .h2(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), H2_ALPN_PROTOCOL), + Some(H2_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} + +#[actix_rt::test] +async fn test_alpn_h2_1() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .finish(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), H2_ALPN_PROTOCOL), + Some(H2_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), HTTP1_1_ALPN_PROTOCOL), + Some(HTTP1_1_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + get_negotiated_alpn_protocol(srv.addr(), CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} diff --git a/src/server.rs b/src/server.rs index 44ae6f880..80e300b9a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -368,7 +368,7 @@ where #[cfg(feature = "rustls")] /// Use listener for accepting incoming tls connection requests /// - /// This method sets alpn protocols to "h2" and "http/1.1" + /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones pub fn listen_rustls( self, lst: net::TcpListener, @@ -482,7 +482,7 @@ where #[cfg(feature = "rustls")] /// Start listening for incoming tls connections. /// - /// This method sets alpn protocols to "h2" and "http/1.1" + /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones pub fn bind_rustls( mut self, addr: A, From fb2b362b6020bad74b994231c563dfa153f50e70 Mon Sep 17 00:00:00 2001 From: peter-formlogic <69773350+peter-formlogic@users.noreply.github.com> Date: Thu, 17 Jun 2021 00:22:49 +0930 Subject: [PATCH 009/861] Adjust JSON limit to 2MB and report on sizes (#2162) Co-authored-by: Rob Ede --- CHANGES.md | 3 +++ src/error/mod.rs | 28 ++++++++++++++++++---- src/types/json.rs | 59 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4a1742c95..ae1d435cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ * `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] ### Changed + +* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] +[#2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] * `ServiceResponse::checked_expr` now returns a `Result`. [#2201] * Update `language-tags` to `0.3`. diff --git a/src/error/mod.rs b/src/error/mod.rs index 7be9f501b..146146c71 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -93,9 +93,17 @@ impl ResponseError for UrlencodedError { #[derive(Debug, Display, Error)] #[non_exhaustive] pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 32kB) - #[display(fmt = "Json payload size is bigger than allowed")] - Overflow, + /// Payload size is bigger than allowed & content length header set. (default: 2MB) + #[display( + fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).", + length, + limit + )] + OverflowKnownLength { length: usize, limit: usize }, + + /// Payload size is bigger than allowed but no content length header set. (default: 2MB) + #[display(fmt = "JSON payload has exceeded limit ({} bytes).", limit)] + Overflow { limit: usize }, /// Content type error #[display(fmt = "Content type error")] @@ -123,7 +131,11 @@ impl From for JsonPayloadError { impl ResponseError for JsonPayloadError { fn status_code(&self) -> StatusCode { match self { - Self::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + Self::OverflowKnownLength { + length: _, + limit: _, + } => StatusCode::PAYLOAD_TOO_LARGE, + Self::Overflow { limit: _ } => StatusCode::PAYLOAD_TOO_LARGE, Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::Payload(err) => err.status_code(), _ => StatusCode::BAD_REQUEST, @@ -208,7 +220,13 @@ mod tests { #[test] fn test_json_payload_error() { - let resp = JsonPayloadError::Overflow.error_response(); + let resp = JsonPayloadError::OverflowKnownLength { + length: 0, + limit: 0, + } + .error_response(); + assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); + let resp = JsonPayloadError::Overflow { limit: 0 }.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); let resp = JsonPayloadError::ContentType.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); diff --git a/src/types/json.rs b/src/types/json.rs index 5762c6428..24abcecea 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -240,7 +240,7 @@ pub struct JsonConfig { } impl JsonConfig { - /// Set maximum accepted payload size. By default this limit is 32kB. + /// Set maximum accepted payload size. By default this limit is 2MB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -273,9 +273,11 @@ impl JsonConfig { } } +const DEFAULT_LIMIT: usize = 2_097_152; // 2 mb + /// Allow shared refs used as default. const DEFAULT_CONFIG: JsonConfig = JsonConfig { - limit: 32_768, // 2^15 bytes, (~32kB) + limit: DEFAULT_LIMIT, err_handler: None, content_type: None, }; @@ -349,7 +351,7 @@ where let payload = payload.take(); JsonBody::Body { - limit: 32_768, + limit: DEFAULT_LIMIT, length, payload, buf: BytesMut::with_capacity(8192), @@ -357,7 +359,7 @@ where } } - /// Set maximum accepted payload size. The default limit is 32kB. + /// Set maximum accepted payload size. The default limit is 2MB. pub fn limit(self, limit: usize) -> Self { match self { JsonBody::Body { @@ -368,7 +370,10 @@ where } => { if let Some(len) = length { if len > limit { - return JsonBody::Error(Some(JsonPayloadError::Overflow)); + return JsonBody::Error(Some(JsonPayloadError::OverflowKnownLength { + length: len, + limit, + })); } } @@ -405,8 +410,11 @@ where match res { Some(chunk) => { let chunk = chunk?; - if (buf.len() + chunk.len()) > *limit { - return Poll::Ready(Err(JsonPayloadError::Overflow)); + let buf_len = buf.len() + chunk.len(); + if buf_len > *limit { + return Poll::Ready(Err(JsonPayloadError::Overflow { + limit: *limit, + })); } else { buf.extend_from_slice(&chunk); } @@ -445,7 +453,12 @@ mod tests { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { - JsonPayloadError::Overflow => matches!(other, JsonPayloadError::Overflow), + JsonPayloadError::Overflow { .. } => { + matches!(other, JsonPayloadError::Overflow { .. }) + } + JsonPayloadError::OverflowKnownLength { .. } => { + matches!(other, JsonPayloadError::OverflowKnownLength { .. }) + } JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), _ => false, } @@ -538,7 +551,7 @@ mod tests { let s = Json::::from_request(&req, &mut pl).await; assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed")); + .contains("JSON payload (16 bytes) is larger than allowed (limit: 10 bytes).")); let (req, mut pl) = TestRequest::default() .insert_header(( @@ -589,7 +602,30 @@ mod tests { let json = JsonBody::::new(&req, &mut pl, None) .limit(100) .await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::OverflowKnownLength { + length: 10000, + limit: 100 + } + )); + + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + )) + .set_payload(Bytes::from_static(&[0u8; 1000])) + .to_http_parts(); + + let json = JsonBody::::new(&req, &mut pl, None) + .limit(100) + .await; + + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Overflow { limit: 100 } + )); let (req, mut pl) = TestRequest::default() .insert_header(( @@ -686,6 +722,7 @@ mod tests { assert!(s.is_err()); let err_str = s.err().unwrap().to_string(); - assert!(err_str.contains("Json payload size is bigger than allowed")); + assert!(err_str + .contains("JSON payload (16 bytes) is larger than allowed (limit: 10 bytes).")); } } From 8d124713fc00d8da1f85e05a560faf70280fbf40 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 16 Jun 2021 22:33:22 +0300 Subject: [PATCH 010/861] files: inline disposition for common web app file types (#2257) --- actix-files/CHANGES.md | 2 ++ actix-files/src/lib.rs | 16 ++++++++++++++++ actix-files/src/named.rs | 13 ++++++++++--- actix-files/tests/test.js | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 actix-files/tests/test.js diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 12b70c135..bd4030e6e 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,10 +4,12 @@ * `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] * For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] * `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 [#2225]: https://github.com/actix/actix-web/pull/2225 +[#2257]: https://github.com/actix/actix-web/pull/2257 ## 0.6.0-beta.4 - 2021-04-02 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index aa5960b5b..48d3c49f4 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -279,6 +279,22 @@ mod tests { ); } + #[actix_rt::test] + async fn test_named_file_javascript() { + let file = NamedFile::open("tests/test.js").unwrap(); + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/javascript" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.js\"" + ); + } + #[actix_rt::test] async fn test_named_file_image_attachment() { let cd = ContentDisposition { diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2183eab5f..37f8def3e 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -120,6 +120,11 @@ impl NamedFile { let disposition = match ct.type_() { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + mime::APPLICATION => match ct.subtype() { + mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, + name if name == "wasm" => DispositionType::Inline, + _ => DispositionType::Attachment, + }, _ => DispositionType::Attachment, }; @@ -213,9 +218,11 @@ impl NamedFile { /// Set the Content-Disposition for serving this file. This allows /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method + /// sent to the peer. + /// + /// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and + /// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, + /// and the filename is taken from the path provided in the `open` method /// after converting it to UTF-8 using. /// [`std::ffi::OsStr::to_string_lossy`] #[inline] diff --git a/actix-files/tests/test.js b/actix-files/tests/test.js new file mode 100644 index 000000000..2ee135561 --- /dev/null +++ b/actix-files/tests/test.js @@ -0,0 +1 @@ +// this file is empty. From bb0331ae28db4db1475136fbbc429e50405a8b69 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 17 Jun 2021 16:49:31 +0100 Subject: [PATCH 011/861] fix cargo cache on msrv --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 585b3f497..c57db463a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,5 +123,5 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean cargo-cache From 532f7b9923720df8f9057ff7941f7f310c5d3ccd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 17 Jun 2021 17:57:58 +0100 Subject: [PATCH 012/861] refined error model (#2253) --- .cargo/config.toml | 5 +- CHANGES.md | 3 + actix-files/Cargo.toml | 1 + actix-http/CHANGES.md | 4 + actix-http/Cargo.toml | 3 +- actix-http/examples/echo.rs | 6 +- actix-http/examples/echo2.rs | 6 +- actix-http/examples/hello-world.rs | 14 +- actix-http/examples/streaming-error.rs | 40 +++ actix-http/src/body/body.rs | 13 +- actix-http/src/body/body_stream.rs | 80 +++++- actix-http/src/body/mod.rs | 6 +- actix-http/src/body/sized_stream.rs | 33 ++- actix-http/src/builder.rs | 45 ++-- actix-http/src/client/error.rs | 40 +-- actix-http/src/encoding/encoder.rs | 30 ++- actix-http/src/error.rs | 324 +++++++++++-------------- actix-http/src/h1/dispatcher.rs | 92 +++---- actix-http/src/h1/encoder.rs | 21 +- actix-http/src/h1/service.rs | 76 +++--- actix-http/src/h1/utils.rs | 13 +- actix-http/src/h2/dispatcher.rs | 26 +- actix-http/src/h2/service.rs | 66 ++--- actix-http/src/helpers.rs | 16 +- actix-http/src/lib.rs | 2 +- actix-http/src/response.rs | 67 +++-- actix-http/src/response_builder.rs | 23 +- actix-http/src/service.rs | 118 ++++----- actix-http/src/ws/dispatcher.rs | 7 +- actix-http/src/ws/mod.rs | 79 +++--- actix-http/tests/test_client.rs | 22 +- actix-http/tests/test_openssl.rs | 44 ++-- actix-http/tests/test_rustls.rs | 50 ++-- actix-http/tests/test_server.rs | 91 +++---- actix-http/tests/test_ws.rs | 45 +++- actix-test/src/lib.rs | 71 +++++- actix-web-actors/src/ws.rs | 9 +- awc/examples/client.rs | 4 +- awc/src/error.rs | 4 - awc/src/frozen.rs | 26 +- awc/src/lib.rs | 3 +- awc/src/request.rs | 29 +-- awc/src/sender.rs | 37 ++- src/data.rs | 3 +- src/error/error.rs | 76 ++++++ src/error/internal.rs | 105 ++++---- src/error/macros.rs | 109 +++++++++ src/error/mod.rs | 8 +- src/error/response_error.rs | 144 +++++++++++ src/extract.rs | 6 +- src/handler.rs | 6 +- src/helpers.rs | 25 ++ src/lib.rs | 21 +- src/middleware/compat.rs | 12 +- src/middleware/compress.rs | 2 +- src/middleware/err_handlers.rs | 2 +- src/request.rs | 7 +- src/request_data.rs | 3 +- src/resource.rs | 20 +- src/responder.rs | 13 +- src/response/builder.rs | 19 +- src/response/response.rs | 16 +- src/route.rs | 18 +- src/server.rs | 48 +++- src/service.rs | 24 +- src/types/form.rs | 2 +- src/types/json.rs | 4 +- src/types/path.rs | 10 +- src/types/query.rs | 2 +- 69 files changed, 1498 insertions(+), 901 deletions(-) create mode 100644 actix-http/examples/streaming-error.rs create mode 100644 src/error/error.rs create mode 100644 src/error/macros.rs create mode 100644 src/error/response_error.rs create mode 100644 src/helpers.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 0bab205cd..0cf09f710 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,8 @@ [alias] -chk = "hack check --workspace --all-features --tests --examples" -lint = "hack --clean-per-run clippy --workspace --tests --examples" +chk = "check --workspace --all-features --tests --examples --bins" +lint = "clippy --workspace --tests --examples" ci-min = "hack check --workspace --no-default-features" ci-min-test = "hack check --workspace --no-default-features --tests --examples" ci-default = "hack check --workspace" ci-full = "check --workspace --bins --examples --tests" +ci-test = "test --workspace --all-features --no-fail-fast" diff --git a/CHANGES.md b/CHANGES.md index ae1d435cc..5d33e6dd1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ * Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] * `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] * `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] * `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] @@ -21,6 +23,7 @@ [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 +[#2253]: https://github.com/actix/actix-web/pull/2253 [#2246]: https://github.com/actix/actix-web/pull/2246 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b97badd3e..6cff9b263 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,6 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.6", default-features = false } +actix-http = "3.0.0-beta.6" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 99953ff26..f25e14254 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -13,12 +13,15 @@ ### Changed * The `MessageBody` trait now has an associated `Error` type. [#2183] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] * Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] * `header` mod is now public. [#2171] * `uri` mod is now public. [#2171] * Update `language-tags` to `0.3`. * Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] * `ResponseBuilder::message_body` now returns a `Result`. [#2201] +* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] * `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed @@ -37,6 +40,7 @@ [#2201]: https://github.com/actix/actix-web/pull/2201 [#2205]: https://github.com/actix/actix-web/pull/2205 [#2215]: https://github.com/actix/actix-web/pull/2215 +[#2253]: https://github.com/actix/actix-web/pull/2253 [#2244]: https://github.com/actix/actix-web/pull/2244 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1a0a32599..ea338fec1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -84,7 +84,8 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } -criterion = "0.3" +async-stream = "0.3" +criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" rcgen = "0.8" serde = { version = "1.0", features = ["derive"] } diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 54a71a106..6cfe3a675 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -5,14 +5,13 @@ use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; use http::header::HeaderValue; -use log::info; #[actix_rt::main] async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() - .bind("echo", "127.0.0.1:8080", || { + .bind("echo", ("127.0.0.1", 8080), || { HttpService::build() .client_timeout(1000) .client_disconnect(1000) @@ -22,7 +21,8 @@ async fn main() -> io::Result<()> { body.extend_from_slice(&item?); } - info!("request body: {:?}", body); + log::info!("request body: {:?}", body); + Ok::<_, Error>( Response::build(StatusCode::OK) .insert_header(( diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 3974cf20b..db195d65b 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -5,7 +5,6 @@ use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; -use log::info; async fn handle_request(mut req: Request) -> Result, Error> { let mut body = BytesMut::new(); @@ -13,7 +12,8 @@ async fn handle_request(mut req: Request) -> Result, Error> { body.extend_from_slice(&item?) } - info!("request body: {:?}", body); + log::info!("request body: {:?}", body); + Ok(Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .body(body)) @@ -24,7 +24,7 @@ async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() - .bind("echo", "127.0.0.1:8080", || { + .bind("echo", ("127.0.0.1", 8080), || { HttpService::build().finish(handle_request).tcp() })? .run() diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index d51de6f4e..9a593c66a 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,28 +1,28 @@ -use std::io; +use std::{convert::Infallible, io}; use actix_http::{http::StatusCode, HttpService, Response}; use actix_server::Server; -use actix_utils::future; use http::header::HeaderValue; -use log::info; #[actix_rt::main] async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); Server::build() - .bind("hello-world", "127.0.0.1:8080", || { + .bind("hello-world", ("127.0.0.1", 8080), || { HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .finish(|_req| { - info!("{:?}", _req); + .finish(|req| async move { + log::info!("{:?}", req); + let mut res = Response::build(StatusCode::OK); res.insert_header(( "x-head", HeaderValue::from_static("dummy value!"), )); - future::ok::<_, ()>(res.body("Hello world!")) + + Ok::<_, Infallible>(res.body("Hello world!")) }) .tcp() })? diff --git a/actix-http/examples/streaming-error.rs b/actix-http/examples/streaming-error.rs new file mode 100644 index 000000000..3988cbac2 --- /dev/null +++ b/actix-http/examples/streaming-error.rs @@ -0,0 +1,40 @@ +//! Example showing response body (chunked) stream erroring. +//! +//! Test using `nc` or `curl`. +//! ```sh +//! $ curl -vN 127.0.0.1:8080 +//! $ echo 'GET / HTTP/1.1\n\n' | nc 127.0.0.1 8080 +//! ``` + +use std::{convert::Infallible, io, time::Duration}; + +use actix_http::{body::BodyStream, HttpService, Response}; +use actix_server::Server; +use async_stream::stream; +use bytes::Bytes; + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("streaming-error", ("127.0.0.1", 8080), || { + HttpService::build() + .finish(|req| async move { + log::info!("{:?}", req); + let res = Response::ok(); + + Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { + yield Ok(Bytes::from("123")); + yield Ok(Bytes::from("456")); + + actix_rt::time::sleep(Duration::from_millis(1000)).await; + + yield Err(io::Error::new(io::ErrorKind::Other, "")); + }))) + }) + .tcp() + })? + .run() + .await +} diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 3fda8ae11..f04837d07 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -76,7 +76,9 @@ impl MessageBody for AnyBody { // TODO: MSRV 1.51: poll_map_err AnyBody::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + Some(Err(err)) => { + Poll::Ready(Some(Err(Error::new_body().with_cause(err)))) + } Some(Ok(val)) => Poll::Ready(Some(Ok(val))), None => Poll::Ready(None), }, @@ -162,9 +164,10 @@ impl From for AnyBody { } } -impl From> for AnyBody +impl From> for AnyBody where - S: Stream> + 'static, + S: Stream> + 'static, + E: Into> + 'static, { fn from(s: SizedStream) -> Body { AnyBody::from_message(s) @@ -174,7 +177,7 @@ where impl From> for AnyBody where S: Stream> + 'static, - E: Into + 'static, + E: Into> + 'static, { fn from(s: BodyStream) -> Body { AnyBody::from_message(s) @@ -222,7 +225,7 @@ impl MessageBody for BoxAnyBody { ) -> Poll>> { // TODO: MSRV 1.51: poll_map_err match ready!(self.0.as_mut().poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + Some(Err(err)) => Poll::Ready(Some(Err(Error::new_body().with_cause(err)))), Some(Ok(val)) => Poll::Ready(Some(Ok(val))), None => Poll::Ready(None), } diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index ebe872022..f726f4475 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -1,4 +1,5 @@ use std::{ + error::Error as StdError, pin::Pin, task::{Context, Poll}, }; @@ -7,8 +8,6 @@ use bytes::Bytes; use futures_core::{ready, Stream}; use pin_project_lite::pin_project; -use crate::error::Error; - use super::{BodySize, MessageBody}; pin_project! { @@ -24,7 +23,7 @@ pin_project! { impl BodyStream where S: Stream>, - E: Into, + E: Into> + 'static, { pub fn new(stream: S) -> Self { BodyStream { stream } @@ -34,9 +33,9 @@ where impl MessageBody for BodyStream where S: Stream>, - E: Into, + E: Into> + 'static, { - type Error = Error; + type Error = E; fn size(&self) -> BodySize { BodySize::Stream @@ -56,7 +55,7 @@ where let chunk = match ready!(stream.poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, - opt => opt.map(|res| res.map_err(Into::into)), + opt => opt, }; return Poll::Ready(chunk); @@ -66,9 +65,16 @@ where #[cfg(test)] mod tests { - use actix_rt::pin; + use std::{convert::Infallible, time::Duration}; + + use actix_rt::{ + pin, + time::{sleep, Sleep}, + }; use actix_utils::future::poll_fn; - use futures_util::stream; + use derive_more::{Display, Error}; + use futures_core::ready; + use futures_util::{stream, FutureExt as _}; use super::*; use crate::body::to_bytes; @@ -78,7 +84,7 @@ mod tests { let body = BodyStream::new(stream::iter( ["1", "", "2"] .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), + .map(|&v| Ok::<_, Infallible>(Bytes::from(v))), )); pin!(body); @@ -103,9 +109,63 @@ mod tests { let body = BodyStream::new(stream::iter( ["1", "", "2"] .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), + .map(|&v| Ok::<_, Infallible>(Bytes::from(v))), )); assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); } + #[derive(Debug, Display, Error)] + #[display(fmt = "stream error")] + struct StreamErr; + + #[actix_rt::test] + async fn stream_immediate_error() { + let body = BodyStream::new(stream::once(async { Err(StreamErr) })); + assert!(matches!(to_bytes(body).await, Err(StreamErr))); + } + + #[actix_rt::test] + async fn stream_delayed_error() { + let body = + BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)])); + assert!(matches!(to_bytes(body).await, Err(StreamErr))); + + #[pin_project::pin_project(project = TimeDelayStreamProj)] + #[derive(Debug)] + enum TimeDelayStream { + Start, + Sleep(Pin>), + Done, + } + + impl Stream for TimeDelayStream { + type Item = Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + match self.as_mut().get_mut() { + TimeDelayStream::Start => { + let sleep = sleep(Duration::from_millis(1)); + self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep))); + cx.waker().wake_by_ref(); + Poll::Pending + } + + TimeDelayStream::Sleep(ref mut delay) => { + ready!(delay.poll_unpin(cx)); + self.set(TimeDelayStream::Done); + cx.waker().wake_by_ref(); + Poll::Pending + } + + TimeDelayStream::Done => Poll::Ready(Some(Err(StreamErr))), + } + } + } + + let body = BodyStream::new(TimeDelayStream::Start); + assert!(matches!(to_bytes(body).await, Err(StreamErr))); + } } diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 11aff039e..8a08dbd2b 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -191,11 +191,15 @@ mod tests { } #[actix_rt::test] - async fn test_box() { + async fn test_box_and_pin() { let val = Box::new(()); pin!(val); assert_eq!(val.size(), BodySize::Empty); assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); + + let mut val = Box::pin(()); + assert_eq!(val.size(), BodySize::Empty); + assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); } #[actix_rt::test] diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index 4af132389..b6ceb32fe 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -1,4 +1,5 @@ use std::{ + error::Error as StdError, pin::Pin, task::{Context, Poll}, }; @@ -7,15 +8,13 @@ use bytes::Bytes; use futures_core::{ready, Stream}; use pin_project_lite::pin_project; -use crate::error::Error; - use super::{BodySize, MessageBody}; pin_project! { /// Known sized streaming response wrapper. /// - /// This body implementation should be used if total size of stream is known. Data get sent as is - /// without using transfer encoding. + /// This body implementation should be used if total size of stream is known. Data is sent as-is + /// without using chunked transfer encoding. pub struct SizedStream { size: u64, #[pin] @@ -23,20 +22,22 @@ pin_project! { } } -impl SizedStream +impl SizedStream where - S: Stream>, + S: Stream>, + E: Into> + 'static, { pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } } } -impl MessageBody for SizedStream +impl MessageBody for SizedStream where - S: Stream>, + S: Stream>, + E: Into> + 'static, { - type Error = Error; + type Error = E; fn size(&self) -> BodySize { BodySize::Sized(self.size as u64) @@ -66,6 +67,8 @@ where #[cfg(test)] mod tests { + use std::convert::Infallible; + use actix_rt::pin; use actix_utils::future::poll_fn; use futures_util::stream; @@ -77,7 +80,11 @@ mod tests { async fn skips_empty_chunks() { let body = SizedStream::new( 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok::<_, Infallible>(Bytes::from(v))), + ), ); pin!(body); @@ -103,7 +110,11 @@ mod tests { async fn read_to_bytes() { let body = SizedStream::new( 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok::<_, Infallible>(Bytes::from(v))), + ), ); assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 660cd9817..4e68dc920 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,19 +1,16 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::{fmt, net}; +use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc}; use actix_codec::Framed; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; -use crate::body::MessageBody; -use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::Error; -use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; -use crate::h2::H2Service; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpService; -use crate::{ConnectCallback, Extensions}; +use crate::{ + body::{AnyBody, MessageBody}, + config::{KeepAlive, ServiceConfig}, + h1::{self, ExpectHandler, H1Service, UpgradeHandler}, + h2::H2Service, + service::HttpService, + ConnectCallback, Extensions, Request, Response, +}; /// A HTTP service builder /// @@ -34,7 +31,7 @@ pub struct HttpServiceBuilder { impl HttpServiceBuilder where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, { @@ -57,13 +54,13 @@ where impl HttpServiceBuilder where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, X: ServiceFactory, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -123,7 +120,7 @@ where where F: IntoServiceFactory, X1: ServiceFactory, - X1::Error: Into, + X1::Error: Into>, X1::InitError: fmt::Debug, { HttpServiceBuilder { @@ -145,8 +142,8 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder where - F: IntoServiceFactory)>, - U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + F: IntoServiceFactory)>, + U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, { @@ -181,7 +178,7 @@ where where B: MessageBody, F: IntoServiceFactory, - S::Error: Into, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, { @@ -203,12 +200,12 @@ where pub fn h2(self, service: F) -> H2Service where F: IntoServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { let cfg = ServiceConfig::new( self.keep_alive, @@ -226,12 +223,12 @@ where pub fn finish(self, service: F) -> HttpService where F: IntoServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index d27363456..34833503b 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,15 +1,16 @@ -use std::io; +use std::{error::Error as StdError, fmt, io}; use derive_more::{Display, From}; #[cfg(feature = "openssl")] use actix_tls::accept::openssl::SslError; -use crate::error::{Error, ParseError, ResponseError}; -use crate::http::{Error as HttpError, StatusCode}; +use crate::error::{Error, ParseError}; +use crate::http::Error as HttpError; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] +#[non_exhaustive] pub enum ConnectError { /// SSL feature is not enabled #[display(fmt = "SSL is not supported")] @@ -64,6 +65,7 @@ impl From for ConnectError { } #[derive(Debug, Display, From)] +#[non_exhaustive] pub enum InvalidUrl { #[display(fmt = "Missing URL scheme")] MissingScheme, @@ -82,6 +84,7 @@ impl std::error::Error for InvalidUrl {} /// A set of errors that can occur during request sending and response reading #[derive(Debug, Display, From)] +#[non_exhaustive] pub enum SendRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] @@ -115,25 +118,17 @@ pub enum SendRequestError { /// Error sending request body Body(Error), + + /// Other errors that can occur after submitting a request. + #[display(fmt = "{:?}: {}", _1, _0)] + Custom(Box, Box), } impl std::error::Error for SendRequestError {} -/// Convert `SendRequestError` to a server `Response` -impl ResponseError for SendRequestError { - fn status_code(&self) -> StatusCode { - match *self { - SendRequestError::Connect(ConnectError::Timeout) => { - StatusCode::GATEWAY_TIMEOUT - } - SendRequestError::Connect(_) => StatusCode::BAD_REQUEST, - _ => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - /// A set of errors that can occur during freezing a request #[derive(Debug, Display, From)] +#[non_exhaustive] pub enum FreezeRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] @@ -142,15 +137,20 @@ pub enum FreezeRequestError { /// HTTP error #[display(fmt = "{}", _0)] Http(HttpError), + + /// Other errors that can occur after submitting a request. + #[display(fmt = "{:?}: {}", _1, _0)] + Custom(Box, Box), } impl std::error::Error for FreezeRequestError {} impl From for SendRequestError { - fn from(e: FreezeRequestError) -> Self { - match e { - FreezeRequestError::Url(e) => e.into(), - FreezeRequestError::Http(e) => e.into(), + fn from(err: FreezeRequestError) -> Self { + match err { + FreezeRequestError::Url(err) => err.into(), + FreezeRequestError::Http(err) => err.into(), + FreezeRequestError::Custom(err, msg) => SendRequestError::Custom(err, msg), } } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 6adde9be2..36509b371 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -133,9 +133,7 @@ where }, EncoderBodyProj::BoxedStream(ref mut b) => { match ready!(b.as_pin_mut().poll_next(cx)) { - Some(Err(err)) => { - Poll::Ready(Some(Err(EncoderError::Boxed(err.into())))) - } + Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Boxed(err)))), Some(Ok(val)) => Poll::Ready(Some(Ok(val))), None => Poll::Ready(None), } @@ -337,7 +335,7 @@ pub enum EncoderError { Body(E), #[display(fmt = "boxed")] - Boxed(Error), + Boxed(Box), #[display(fmt = "blocking")] Blocking(BlockingError), @@ -346,19 +344,19 @@ pub enum EncoderError { Io(io::Error), } -impl StdError for EncoderError { +impl StdError for EncoderError { fn source(&self) -> Option<&(dyn StdError + 'static)> { - None - } -} - -impl> From> for Error { - fn from(err: EncoderError) -> Self { - match err { - EncoderError::Body(err) => err.into(), - EncoderError::Boxed(err) => err, - EncoderError::Blocking(err) => err.into(), - EncoderError::Io(err) => err.into(), + match self { + EncoderError::Body(err) => Some(err), + EncoderError::Boxed(err) => Some(&**err), + EncoderError::Blocking(err) => Some(err), + EncoderError::Io(err) => Some(err), } } } + +impl From> for crate::Error { + fn from(err: EncoderError) -> Self { + crate::Error::new_encoder().with_cause(err) + } +} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 92efd572d..d9e1a1ed2 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,174 +1,155 @@ //! Error and Result module -use std::{ - error::Error as StdError, - fmt, - io::{self, Write as _}, - str::Utf8Error, - string::FromUtf8Error, -}; +use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error}; -use bytes::BytesMut; use derive_more::{Display, Error, From}; -use http::{header, uri::InvalidUri, StatusCode}; -use serde::de::value::Error as DeError; +use http::{uri::InvalidUri, StatusCode}; -use crate::{body::Body, helpers::Writer, Response}; +use crate::{ + body::{AnyBody, Body}, + ws, Response, +}; pub use http::Error as HttpError; -/// General purpose actix web error. -/// -/// An actix web error is used to carry errors from `std::error` -/// through actix in a convenient way. It can be created through -/// converting errors with `into()`. -/// -/// Whenever it is created from an external object a response error is created -/// for it that can be used to create an HTTP response from it this means that -/// if you have access to an actix `Error` you can always get a -/// `ResponseError` reference from it. pub struct Error { - cause: Box, + inner: Box, +} + +pub(crate) struct ErrorInner { + #[allow(dead_code)] + kind: Kind, + cause: Option>, } impl Error { - /// Returns the reference to the underlying `ResponseError`. - pub fn as_response_error(&self) -> &dyn ResponseError { - self.cause.as_ref() + fn new(kind: Kind) -> Self { + Self { + inner: Box::new(ErrorInner { kind, cause: None }), + } } - /// Similar to `as_response_error` but downcasts. - pub fn as_error(&self) -> Option<&T> { - ::downcast_ref(self.cause.as_ref()) + pub(crate) fn new_http() -> Self { + Self::new(Kind::Http) + } + + pub(crate) fn new_parse() -> Self { + Self::new(Kind::Parse) + } + + pub(crate) fn new_payload() -> Self { + Self::new(Kind::Payload) + } + + pub(crate) fn new_body() -> Self { + Self::new(Kind::Body) + } + + pub(crate) fn new_send_response() -> Self { + Self::new(Kind::SendResponse) + } + + // TODO: remove allow + #[allow(dead_code)] + pub(crate) fn new_io() -> Self { + Self::new(Kind::Io) + } + + pub(crate) fn new_encoder() -> Self { + Self::new(Kind::Encoder) + } + + pub(crate) fn new_ws() -> Self { + Self::new(Kind::Ws) + } + + pub(crate) fn with_cause(mut self, cause: impl Into>) -> Self { + self.inner.cause = Some(cause.into()); + self } } -/// Errors that can generate responses. -pub trait ResponseError: fmt::Debug + fmt::Display { - /// Returns appropriate status code for error. - /// - /// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is - /// also implemented and does not call `self.status_code()`, then this will not be used. - fn status_code(&self) -> StatusCode { - StatusCode::INTERNAL_SERVER_ERROR - } +impl From for Response { + fn from(err: Error) -> Self { + let status_code = match err.inner.kind { + Kind::Parse => StatusCode::BAD_REQUEST, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }; - /// Creates full response for error. - /// - /// By default, the generated response uses a 500 Internal Server Error status code, a - /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl. - fn error_response(&self) -> Response { - let mut resp = Response::new(self.status_code()); - let mut buf = BytesMut::new(); - let _ = write!(Writer(&mut buf), "{}", self); - resp.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); - resp.set_body(Body::from(buf)) + Response::new(status_code).set_body(Body::from(err.to_string())) } - - downcast_get_type_id!(); } -downcast!(ResponseError); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] +pub enum Kind { + #[display(fmt = "error processing HTTP")] + Http, -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } + #[display(fmt = "error parsing HTTP message")] + Parse, + + #[display(fmt = "request payload read error")] + Payload, + + #[display(fmt = "response body write error")] + Body, + + #[display(fmt = "send response error")] + SendResponse, + + #[display(fmt = "error in WebSocket process")] + Ws, + + #[display(fmt = "connection error")] + Io, + + #[display(fmt = "encoder error")] + Encoder, } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", &self.cause) + // TODO: more detail + f.write_str("actix_http::Error") } } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.inner.cause.as_ref() { + Some(err) => write!(f, "{}: {}", &self.inner.kind, err), + None => write!(f, "{}", &self.inner.kind), + } } } -impl From<()> for Error { - fn from(_: ()) -> Self { - Error::from(UnitError) +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.inner.cause.as_ref().map(|err| err.as_ref()) } } impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - // hint that an error that will never happen - unreachable!() + fn from(err: std::convert::Infallible) -> Self { + match err {} } } -/// Convert `Error` to a `Response` instance -impl From for Response { - fn from(err: Error) -> Self { - Response::from_error(err) +impl From for Error { + fn from(err: ws::ProtocolError) -> Self { + Self::new_ws().with_cause(err) } } -/// `Error` for any error that implements `ResponseError` -impl From for Error { - fn from(err: T) -> Error { - Error { - cause: Box::new(err), - } +impl From for Error { + fn from(err: HttpError) -> Self { + Self::new_http().with_cause(err) } } -#[derive(Debug, Display, Error)] -#[display(fmt = "Unknown Error")] -struct UnitError; - -impl ResponseError for Box {} - -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. -impl ResponseError for UnitError {} - -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`]. -#[cfg(feature = "openssl")] -impl ResponseError for actix_tls::accept::openssl::SslError {} - -/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`]. -impl ResponseError for DeError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`]. -impl ResponseError for Utf8Error { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`HttpError`]. -impl ResponseError for HttpError {} - -/// Inspects the underlying [`io::ErrorKind`] and returns an appropriate status code. -/// -/// If the error is [`io::ErrorKind::NotFound`], [`StatusCode::NOT_FOUND`] is returned. If the -/// error is [`io::ErrorKind::PermissionDenied`], [`StatusCode::FORBIDDEN`] is returned. Otherwise, -/// [`StatusCode::INTERNAL_SERVER_ERROR`] is returned. -impl ResponseError for io::Error { - fn status_code(&self) -> StatusCode { - match self.kind() { - io::ErrorKind::NotFound => StatusCode::NOT_FOUND, - io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, - _ => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -/// Returns [`StatusCode::BAD_REQUEST`] for [`header::InvalidHeaderValue`]. -impl ResponseError for header::InvalidHeaderValue { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST +impl From for Error { + fn from(err: ws::HandshakeError) -> Self { + Self::new_ws().with_cause(err) } } @@ -218,13 +199,6 @@ pub enum ParseError { Utf8(Utf8Error), } -/// Return `BadRequest` for `ParseError` -impl ResponseError for ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - impl From for ParseError { fn from(err: io::Error) -> ParseError { ParseError::Io(err) @@ -263,14 +237,23 @@ impl From for ParseError { } } +impl From for Error { + fn from(err: ParseError) -> Self { + Self::new_parse().with_cause(err) + } +} + +impl From for Response { + fn from(err: ParseError) -> Self { + Error::from(err).into() + } +} + /// A set of errors that can occur running blocking tasks in thread pool. #[derive(Debug, Display, Error)] #[display(fmt = "Blocking thread pool is gone")] pub struct BlockingError; -/// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} - /// A set of errors that can occur during payload parsing. #[derive(Debug, Display)] #[non_exhaustive] @@ -344,16 +327,9 @@ impl From for PayloadError { } } -/// `PayloadError` returns two possible results: -/// -/// - `Overflow` returns `PayloadTooLarge` -/// - Other errors returns `BadRequest` -impl ResponseError for PayloadError { - fn status_code(&self) -> StatusCode { - match *self { - PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, - _ => StatusCode::BAD_REQUEST, - } +impl From for Error { + fn from(err: PayloadError) -> Self { + Self::new_payload().with_cause(err) } } @@ -362,13 +338,19 @@ impl ResponseError for PayloadError { #[non_exhaustive] pub enum DispatchError { /// Service error - Service(Error), + // FIXME: display and error type + #[display(fmt = "Service Error")] + Service(#[error(not(source))] Response), + + /// Body error + // FIXME: display and error type + #[display(fmt = "Body Error")] + Body(#[error(not(source))] Box), /// Upgrade service error Upgrade, - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. + /// An `io::Error` that occurred while trying to read or write to a network stream. #[display(fmt = "IO error: {}", _0)] Io(io::Error), @@ -434,12 +416,6 @@ mod content_type_test_impls { } } -impl ResponseError for ContentTypeError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - #[cfg(test)] mod tests { use super::*; @@ -448,42 +424,36 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = ParseError::Incomplete.error_response(); + let resp: Response = ParseError::Incomplete.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: Response = err.error_response(); + let resp: Response = Error::new_http().with_cause(err).into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); - let e: Error = ParseError::Io(orig).into(); - assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); - } - - #[test] - fn test_error_cause() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.to_string(); - let e = Error::from(orig); - assert_eq!(format!("{}", e.as_response_error()), desc); + let err: Error = ParseError::Io(orig).into(); + assert_eq!( + format!("{}", err), + "error parsing HTTP message: IO error: other" + ); } #[test] fn test_error_display() { let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.to_string(); - let e = Error::from(orig); - assert_eq!(format!("{}", e), desc); + let err = Error::new_io().with_cause(orig); + assert_eq!("connection error: other", err.to_string()); } #[test] fn test_error_http_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); - let e = Error::from(orig); - let resp: Response = e.into(); + let err = Error::new_io().with_cause(orig); + let resp: Response = err.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -535,14 +505,4 @@ mod tests { from!(httparse::Error::TooManyHeaders => ParseError::TooLarge); from!(httparse::Error::Version => ParseError::Version); } - - #[test] - fn test_error_casting() { - let err = PayloadError::Overflow; - let resp_err: &dyn ResponseError = &err; - let err = resp_err.downcast_ref::().unwrap(); - assert_eq!(err.to_string(), "Payload reached size limit."); - let not_err = resp_err.downcast_ref::(); - assert!(not_err.is_none()); - } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c81d0b3bc..b4adde638 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,5 +1,6 @@ use std::{ collections::VecDeque, + error::Error as StdError, fmt, future::Future, io, mem, net, @@ -17,19 +18,19 @@ use futures_core::ready; use log::{error, trace}; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody}; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::error::{ParseError, PayloadError}; -use crate::http::StatusCode; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpFlow; -use crate::OnConnectData; +use crate::{ + body::{AnyBody, BodySize, MessageBody}, + config::ServiceConfig, + error::{DispatchError, ParseError, PayloadError}, + service::HttpFlow, + OnConnectData, Request, Response, StatusCode, +}; -use super::codec::Codec; -use super::payload::{Payload, PayloadSender, PayloadStatus}; -use super::{Message, MessageType}; +use super::{ + codec::Codec, + payload::{Payload, PayloadSender, PayloadStatus}, + Message, MessageType, +}; const LW_BUFFER_SIZE: usize = 1024; const HW_BUFFER_SIZE: usize = 1024 * 8; @@ -50,13 +51,13 @@ bitflags! { pub struct Dispatcher where S: Service, - S::Error: Into, + S::Error: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -72,13 +73,13 @@ where enum DispatcherState where S: Service, - S::Error: Into, + S::Error: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -91,13 +92,13 @@ where struct InnerDispatcher where S: Service, - S::Error: Into, + S::Error: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -136,13 +137,13 @@ where X: Service, B: MessageBody, - B::Error: Into, + B::Error: Into>, { None, ExpectCall(#[pin] X::Future), ServiceCall(#[pin] S::Future), SendPayload(#[pin] B), - SendErrorPayload(#[pin] Body), + SendErrorPayload(#[pin] AnyBody), } impl State @@ -152,7 +153,7 @@ where X: Service, B: MessageBody, - B::Error: Into, + B::Error: Into>, { fn is_empty(&self) -> bool { matches!(self, State::None) @@ -170,14 +171,14 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -231,14 +232,14 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -334,7 +335,7 @@ where fn send_error_response( mut self: Pin<&mut Self>, message: Response<()>, - body: Body, + body: AnyBody, ) -> Result<(), DispatchError> { let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { @@ -379,7 +380,7 @@ where // send_response would update InnerDispatcher state to SendPayload or // None(If response body is empty). // continue loop to poll it. - self.as_mut().send_error_response(res, Body::Empty)?; + self.as_mut().send_error_response(res, AnyBody::Empty)?; } // return with upgrade request and poll it exclusively. @@ -399,7 +400,7 @@ where // send service call error as response Poll::Ready(Err(err)) => { - let res = Response::from_error(err); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -438,7 +439,7 @@ where } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Service(err.into())) + return Err(DispatchError::Body(err.into())) } Poll::Pending => return Ok(PollResponse::DoNothing), @@ -473,7 +474,7 @@ where } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Service(err)) + return Err(DispatchError::Service(err.into())) } Poll::Pending => return Ok(PollResponse::DoNothing), @@ -496,7 +497,7 @@ where // send expect error as response Poll::Ready(Err(err)) => { - let res = Response::from_error(err); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -546,7 +547,7 @@ where // to notify the dispatcher a new state is set and the outer loop // should be continue. Poll::Ready(Err(err)) => { - let res = Response::from_error(err); + let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_error_response(res, body); } @@ -566,7 +567,7 @@ where Poll::Pending => Ok(()), // see the comment on ExpectCall state branch's Ready(Err(err)). Poll::Ready(Err(err)) => { - let res = Response::from_error(err); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.send_error_response(res, body) } @@ -772,7 +773,7 @@ where trace!("Slow request timeout"); let _ = self.as_mut().send_error_response( Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), - Body::Empty, + AnyBody::Empty, ); this = self.project(); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); @@ -909,14 +910,14 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -1067,16 +1068,17 @@ mod tests { } } - fn ok_service() -> impl Service, Error = Error> { + fn ok_service() -> impl Service, Error = Error> + { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) } fn echo_path_service( - ) -> impl Service, Error = Error> { + ) -> impl Service, Error = Error> { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>( - Response::ok().set_body(Body::from_slice(path)), + Response::ok().set_body(AnyBody::from_slice(path)), )) }) } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index eaabcb687..254981123 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -6,14 +6,15 @@ use std::{cmp, io}; use bytes::{BufMut, BytesMut}; -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::header::{map::Value, HeaderName}; -use crate::helpers; -use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use crate::http::{HeaderMap, StatusCode, Version}; -use crate::message::{ConnectionType, RequestHeadType}; -use crate::response::Response; +use crate::{ + body::BodySize, + config::ServiceConfig, + header::{map::Value, HeaderMap, HeaderName}, + header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, + helpers, + message::{ConnectionType, RequestHeadType}, + Response, StatusCode, Version, +}; const AVERAGE_HEADER_SIZE: usize = 30; @@ -287,7 +288,7 @@ impl MessageType for RequestHeadType { let head = self.as_ref(); dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); write!( - helpers::Writer(dst), + helpers::MutWriter(dst), "{} {} {}", head.method, head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), @@ -420,7 +421,7 @@ impl TransferEncoding { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } else { - writeln!(helpers::Writer(buf), "{:X}\r", msg.len()) + writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; buf.reserve(msg.len() + 2); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 1ab85cbf3..dbad8cfac 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,7 +1,11 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, net}; +use std::{ + error::Error as StdError, + fmt, + marker::PhantomData, + net, + rc::Rc, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; @@ -11,17 +15,15 @@ use actix_service::{ use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; -use crate::body::MessageBody; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpServiceHandler; -use crate::{ConnectCallback, OnConnectData}; +use crate::{ + body::{AnyBody, MessageBody}, + config::ServiceConfig, + error::DispatchError, + service::HttpServiceHandler, + ConnectCallback, OnConnectData, Request, Response, +}; -use super::codec::Codec; -use super::dispatcher::Dispatcher; -use super::{ExpectHandler, UpgradeHandler}; +use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler}; /// `ServiceFactory` implementation for HTTP1 transport pub struct H1Service { @@ -36,7 +38,7 @@ pub struct H1Service { impl H1Service where S: ServiceFactory, - S::Error: Into, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, @@ -61,21 +63,21 @@ impl H1Service where S: ServiceFactory, S::Future: 'static, - S::Error: Into, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create simple tcp stream service @@ -110,16 +112,16 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -128,7 +130,7 @@ mod openssl { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create openssl based service @@ -170,16 +172,16 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -188,7 +190,7 @@ mod rustls { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create rustls based service @@ -217,7 +219,7 @@ mod rustls { impl H1Service where S: ServiceFactory, - S::Error: Into, + S::Error: Into>, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, @@ -225,7 +227,7 @@ where pub fn expect(self, expect: X1) -> H1Service where X1: ServiceFactory, - X1::Error: Into, + X1::Error: Into>, X1::InitError: fmt::Debug, { H1Service { @@ -268,21 +270,21 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into, + S::Error: Into>, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { type Response = (); @@ -338,17 +340,17 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, { type Response = (); type Error = DispatchError; diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 90e44daa4..523e652fd 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -81,7 +81,9 @@ where let _ = this.body.take(); } let framed = this.framed.as_mut().as_pin_mut().unwrap(); - framed.write(Message::Chunk(item))?; + framed.write(Message::Chunk(item)).map_err(|err| { + Error::new_send_response().with_cause(err) + })?; } Poll::Pending => body_ready = false, } @@ -92,7 +94,10 @@ where // flush write buffer if !framed.is_write_buf_empty() { - match framed.flush(cx)? { + match framed + .flush(cx) + .map_err(|err| Error::new_send_response().with_cause(err))? + { Poll::Ready(_) => { if body_ready { continue; @@ -106,7 +111,9 @@ where // send response if let Some(res) = this.res.take() { - framed.write(res)?; + framed + .write(res) + .map_err(|err| Error::new_send_response().with_cause(err))?; continue; } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index baff20e51..ea149b1e0 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,5 +1,6 @@ use std::{ cmp, + error::Error as StdError, future::Future, marker::PhantomData, net, @@ -18,15 +19,12 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use log::{error, trace}; use pin_project_lite::pin_project; -use crate::body::{BodySize, MessageBody}; -use crate::config::ServiceConfig; -use crate::error::Error; -use crate::message::ResponseHead; -use crate::payload::Payload; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpFlow; -use crate::OnConnectData; +use crate::{ + body::{AnyBody, BodySize, MessageBody}, + config::ServiceConfig, + service::HttpFlow, + OnConnectData, Payload, Request, Response, ResponseHead, +}; const CHUNK_SIZE: usize = 16_384; @@ -66,12 +64,12 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into, + S::Error: Into>, S::Future: 'static, S::Response: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, { type Output = Result<(), crate::error::DispatchError>; @@ -106,7 +104,7 @@ where let res = match fut.await { Ok(res) => handle_response(res.into(), tx, config).await, Err(err) => { - let res = Response::from_error(err.into()); + let res: Response = err.into(); handle_response(res, tx, config).await } }; @@ -133,7 +131,7 @@ where enum DispatchError { SendResponse(h2::Error), SendData(h2::Error), - ResponseBody(Error), + ResponseBody(Box), } async fn handle_response( @@ -143,7 +141,7 @@ async fn handle_response( ) -> Result<(), DispatchError> where B: MessageBody, - B::Error: Into, + B::Error: Into>, { let (res, body) = res.replace_body(()); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 3a6d535d9..09e24045b 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,8 +1,12 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{net, rc::Rc}; +use std::{ + error::Error as StdError, + future::Future, + marker::PhantomData, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; @@ -13,16 +17,16 @@ use actix_service::{ use actix_utils::future::ready; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; -use h2::server::{handshake, Handshake}; +use h2::server::{handshake as h2_handshake, Handshake as H2Handshake}; use log::error; -use crate::body::MessageBody; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpFlow; -use crate::{ConnectCallback, OnConnectData}; +use crate::{ + body::{AnyBody, MessageBody}, + config::ServiceConfig, + error::DispatchError, + service::HttpFlow, + ConnectCallback, OnConnectData, Request, Response, +}; use super::dispatcher::Dispatcher; @@ -37,12 +41,12 @@ pub struct H2Service { impl H2Service where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create new `H2Service` instance with config. pub(crate) fn with_config>( @@ -68,12 +72,12 @@ impl H2Service where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create plain TCP based service pub fn tcp( @@ -107,12 +111,12 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create OpenSSL based service pub fn openssl( @@ -153,12 +157,12 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create Rustls based service pub fn rustls( @@ -197,12 +201,12 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { type Response = (); type Error = DispatchError; @@ -237,7 +241,7 @@ where impl H2ServiceHandler where S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, @@ -260,11 +264,11 @@ impl Service<(T, Option)> for H2ServiceHandler, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { type Response = (); type Error = DispatchError; @@ -288,7 +292,7 @@ where Some(self.cfg.clone()), addr, on_connect_data, - handshake(io), + h2_handshake(io), ), } } @@ -305,7 +309,7 @@ where Option, Option, OnConnectData, - Handshake, + H2Handshake, ), } @@ -313,7 +317,7 @@ pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, @@ -325,11 +329,11 @@ impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, - B::Error: Into, + B::Error: Into>, { type Output = Result<(), DispatchError>; diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index 34bb989f9..cba94d9b8 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -27,7 +27,9 @@ pub(crate) fn write_status_line(version: Version, n: u16, buf: &mut B buf.put_u8(b' '); } -/// NOTE: bytes object has to contain enough space +/// Write out content length header. +/// +/// Buffer must to contain enough space or be implicitly extendable. pub fn write_content_length(n: u64, buf: &mut B) { if n == 0 { buf.put_slice(b"\r\ncontent-length: 0\r\n"); @@ -41,11 +43,15 @@ pub fn write_content_length(n: u64, buf: &mut B) { buf.put_slice(b"\r\n"); } -// TODO: bench why this is needed vs Buf::writer -/// An `io` writer for a `BufMut` that should only be used once and on an empty buffer. -pub(crate) struct Writer<'a, B>(pub &'a mut B); +/// An `io::Write`r that only requires mutable reference and assumes that there is space available +/// in the buffer for every write operation or that it can be extended implicitly (like +/// `bytes::BytesMut`, for example). +/// +/// This is slightly faster (~10%) than `bytes::buf::Writer` in such cases because it does not +/// perform a remaining length check before writing. +pub(crate) struct MutWriter<'a, B>(pub(crate) &'a mut B); -impl<'a, B> io::Write for Writer<'a, B> +impl<'a, B> io::Write for MutWriter<'a, B> where B: BufMut, { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 7c2c3b4e3..9f94faaa5 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -54,7 +54,7 @@ pub mod ws; pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; -pub use self::error::{Error, ResponseError}; +pub use self::error::Error; pub use self::extensions::Extensions; pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index bcfa65732..2aa38c153 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -8,7 +8,7 @@ use std::{ use bytes::{Bytes, BytesMut}; use crate::{ - body::{Body, MessageBody}, + body::{AnyBody, MessageBody}, error::Error, extensions::Extensions, http::{HeaderMap, StatusCode}, @@ -22,13 +22,13 @@ pub struct Response { pub(crate) body: B, } -impl Response { +impl Response { /// Constructs a new response with default body. #[inline] - pub fn new(status: StatusCode) -> Response { + pub fn new(status: StatusCode) -> Self { Response { head: BoxedResponseHead::new(status), - body: Body::Empty, + body: AnyBody::Empty, } } @@ -43,40 +43,29 @@ impl Response { /// Constructs a new response with status 200 OK. #[inline] - pub fn ok() -> Response { + pub fn ok() -> Self { Response::new(StatusCode::OK) } /// Constructs a new response with status 400 Bad Request. #[inline] - pub fn bad_request() -> Response { + pub fn bad_request() -> Self { Response::new(StatusCode::BAD_REQUEST) } /// Constructs a new response with status 404 Not Found. #[inline] - pub fn not_found() -> Response { + pub fn not_found() -> Self { Response::new(StatusCode::NOT_FOUND) } /// Constructs a new response with status 500 Internal Server Error. #[inline] - pub fn internal_server_error() -> Response { + pub fn internal_server_error() -> Self { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } // end shortcuts - - /// Constructs a new response from an error. - #[inline] - pub fn from_error(error: impl Into) -> Response { - let error = error.into(); - let resp = error.as_response_error().error_response(); - if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { - debug!("Internal Server Error: {:?}", error); - } - resp - } } impl Response { @@ -209,7 +198,6 @@ impl Response { impl fmt::Debug for Response where B: MessageBody, - B::Error: Into, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let res = writeln!( @@ -235,7 +223,9 @@ impl Default for Response { } } -impl>, E: Into> From> for Response { +impl>, E: Into> From> + for Response +{ fn from(res: Result) -> Self { match res { Ok(val) => val.into(), @@ -244,13 +234,19 @@ impl>, E: Into> From> for Response for Response { +impl From for Response { fn from(mut builder: ResponseBuilder) -> Self { builder.finish() } } -impl From<&'static str> for Response { +impl From for Response { + fn from(val: std::convert::Infallible) -> Self { + match val {} + } +} + +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) @@ -258,7 +254,7 @@ impl From<&'static str> for Response { } } -impl From<&'static [u8]> for Response { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) @@ -266,7 +262,7 @@ impl From<&'static [u8]> for Response { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) @@ -274,7 +270,7 @@ impl From for Response { } } -impl<'a> From<&'a String> for Response { +impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { Response::build(StatusCode::OK) .content_type(mime::TEXT_PLAIN_UTF_8) @@ -282,7 +278,7 @@ impl<'a> From<&'a String> for Response { } } -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) @@ -290,7 +286,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::build(StatusCode::OK) .content_type(mime::APPLICATION_OCTET_STREAM) @@ -301,7 +297,6 @@ impl From for Response { #[cfg(test)] mod tests { use super::*; - use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; #[test] @@ -316,7 +311,7 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = "test".into(); + let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -325,7 +320,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = b"test".as_ref().into(); + let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -334,7 +329,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = "test".to_owned().into(); + let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -343,7 +338,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); - let resp: Response = (&"test".to_owned()).into(); + let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -353,7 +348,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -363,7 +358,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -373,7 +368,7 @@ mod tests { assert_eq!(resp.body().get_ref(), b"test"); let b = BytesMut::from("test"); - let resp: Response = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index df9079d70..e46d9a28c 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -2,6 +2,7 @@ use std::{ cell::{Ref, RefMut}, + error::Error as StdError, fmt, future::Future, pin::Pin, @@ -13,7 +14,7 @@ use bytes::Bytes; use futures_core::Stream; use crate::{ - body::{Body, BodyStream}, + body::{AnyBody, BodyStream}, error::{Error, HttpError}, header::{self, IntoHeaderPair, IntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, @@ -235,9 +236,9 @@ impl ResponseBuilder { /// /// This `ResponseBuilder` will be left in a useless state. #[inline] - pub fn body>(&mut self, body: B) -> Response { + pub fn body>(&mut self, body: B) -> Response { self.message_body(body.into()) - .unwrap_or_else(Response::from_error) + .unwrap_or_else(Response::from) } /// Generate response with a body. @@ -245,7 +246,7 @@ impl ResponseBuilder { /// This `ResponseBuilder` will be left in a useless state. pub fn message_body(&mut self, body: B) -> Result, Error> { if let Some(err) = self.err.take() { - return Err(err.into()); + return Err(Error::new_http().with_cause(err)); } let head = self.head.take().expect("cannot reuse response builder"); @@ -256,20 +257,20 @@ impl ResponseBuilder { /// /// This `ResponseBuilder` will be left in a useless state. #[inline] - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where - S: Stream> + Unpin + 'static, - E: Into + 'static, + S: Stream> + 'static, + E: Into> + 'static, { - self.body(Body::from_message(BodyStream::new(stream))) + self.body(AnyBody::from_message(BodyStream::new(stream))) } /// Generate response with an empty body. /// /// This `ResponseBuilder` will be left in a useless state. #[inline] - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) + pub fn finish(&mut self) -> Response { + self.body(AnyBody::Empty) } /// Create an owned `ResponseBuilder`, leaving the original in a useless state. @@ -327,7 +328,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } impl Future for ResponseBuilder { - type Output = Result, Error>; + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { Poll::Ready(Ok(self.finish())) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1c81e7568..afe47bf2d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,4 +1,5 @@ use std::{ + error::Error as StdError, fmt, future::Future, marker::PhantomData, @@ -8,6 +9,7 @@ use std::{ task::{Context, Poll}, }; +use ::h2::server::{handshake as h2_handshake, Handshake as H2Handshake}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{ @@ -15,16 +17,15 @@ use actix_service::{ }; use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; -use h2::server::{handshake, Handshake}; use pin_project::pin_project; -use crate::body::MessageBody; -use crate::builder::HttpServiceBuilder; -use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::{DispatchError, Error}; -use crate::request::Request; -use crate::response::Response; -use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol}; +use crate::{ + body::{AnyBody, MessageBody}, + builder::HttpServiceBuilder, + config::{KeepAlive, ServiceConfig}, + error::DispatchError, + h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, +}; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. pub struct HttpService { @@ -39,7 +40,7 @@ pub struct HttpService { impl HttpService where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, @@ -54,12 +55,12 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -94,7 +95,7 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, @@ -108,7 +109,7 @@ where pub fn expect(self, expect: X1) -> HttpService where X1: ServiceFactory, - X1::Error: Into, + X1::Error: Into>, X1::InitError: fmt::Debug, { HttpService { @@ -152,17 +153,17 @@ impl HttpService where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -171,7 +172,7 @@ where Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create simple tcp stream service @@ -204,17 +205,17 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -223,7 +224,7 @@ mod openssl { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create openssl based service @@ -272,17 +273,17 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -291,7 +292,7 @@ mod rustls { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create rustls based service @@ -338,22 +339,22 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { type Response = (); @@ -416,11 +417,11 @@ where impl HttpServiceHandler where S: Service, - S::Error: Into, + S::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed)>, - U::Error: Into, + U::Error: Into>, { pub(super) fn new( cfg: ServiceConfig, @@ -437,7 +438,10 @@ where } } - pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + pub(super) fn _poll_ready( + &self, + cx: &mut Context<'_>, + ) -> Poll>> { ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?; ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?; @@ -473,18 +477,18 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display + Into, + U::Error: fmt::Display + Into>, { type Response = (); type Error = DispatchError; @@ -507,7 +511,7 @@ where match proto { Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake(Some(( - handshake(io), + h2_handshake(io), self.cfg.clone(), self.flow.clone(), on_connect_data, @@ -537,22 +541,22 @@ where S: Service, S::Future: 'static, - S::Error: Into, + S::Error: Into>, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { H1(#[pin] h1::Dispatcher), - H2(#[pin] Dispatcher), + H2(#[pin] h2::Dispatcher), H2Handshake( Option<( - Handshake, + H2Handshake, ServiceConfig, Rc>, OnConnectData, @@ -567,15 +571,15 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -589,15 +593,15 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, X: Service, - X::Error: Into, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -613,13 +617,15 @@ where Ok(conn) => { let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap(); - self.as_mut().project().state.set(State::H2(Dispatcher::new( - srv, - conn, - on_connect_data, - cfg, - peer_addr, - ))); + self.as_mut().project().state.set(State::H2( + h2::Dispatcher::new( + srv, + conn, + on_connect_data, + cfg, + peer_addr, + ), + )); self.poll(cx) } Err(err) => { diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 576851139..f49cbe5d4 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -72,7 +72,7 @@ mod inner { use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; - use crate::ResponseError; + use crate::{body::AnyBody, Response}; /// Framed transport errors pub enum DispatcherError @@ -136,13 +136,16 @@ mod inner { } } - impl ResponseError for DispatcherError + impl From> for Response where E: fmt::Debug + fmt::Display, U: Encoder + Decoder, >::Error: fmt::Debug, ::Error: fmt::Debug, { + fn from(err: DispatcherError) -> Self { + Response::internal_server_error().set_body(AnyBody::from(err.to_string())) + } } /// Message type wrapper for signalling end of message stream. diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 22df2b4ff..7df924cf5 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,8 +9,8 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::{ - body::Body, error::ResponseError, header::HeaderValue, message::RequestHead, - response::Response, ResponseBuilder, + body::AnyBody, header::HeaderValue, message::RequestHead, response::Response, + ResponseBuilder, }; mod codec; @@ -25,7 +25,7 @@ pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; /// WebSocket protocol errors. -#[derive(Debug, Display, From, Error)] +#[derive(Debug, Display, Error, From)] pub enum ProtocolError { /// Received an unmasked frame from client. #[display(fmt = "Received an unmasked frame from client.")] @@ -68,10 +68,8 @@ pub enum ProtocolError { Io(io::Error), } -impl ResponseError for ProtocolError {} - /// WebSocket handshake errors -#[derive(PartialEq, Debug, Display)] +#[derive(Debug, PartialEq, Display, Error)] pub enum HandshakeError { /// Only get method is allowed. #[display(fmt = "Method not allowed.")] @@ -98,44 +96,55 @@ pub enum HandshakeError { BadWebsocketKey, } -impl ResponseError for HandshakeError { - fn error_response(&self) -> Response { - match self { +impl From<&HandshakeError> for Response { + fn from(err: &HandshakeError) -> Self { + match err { HandshakeError::GetMethodRequired => { - Response::build(StatusCode::METHOD_NOT_ALLOWED) - .insert_header((header::ALLOW, "GET")) - .finish() + let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED); + res.headers_mut() + .insert(header::ALLOW, HeaderValue::from_static("GET")); + res } HandshakeError::NoWebsocketUpgrade => { - Response::build(StatusCode::BAD_REQUEST) - .reason("No WebSocket Upgrade header found") - .finish() + let mut res = Response::bad_request(); + res.head_mut().reason = Some("No WebSocket Upgrade header found"); + res } HandshakeError::NoConnectionUpgrade => { - Response::build(StatusCode::BAD_REQUEST) - .reason("No Connection upgrade") - .finish() + let mut res = Response::bad_request(); + res.head_mut().reason = Some("No Connection upgrade"); + res } - HandshakeError::NoVersionHeader => Response::build(StatusCode::BAD_REQUEST) - .reason("WebSocket version header is required") - .finish(), + HandshakeError::NoVersionHeader => { + let mut res = Response::bad_request(); + res.head_mut().reason = Some("WebSocket version header is required"); + res + } HandshakeError::UnsupportedVersion => { - Response::build(StatusCode::BAD_REQUEST) - .reason("Unsupported WebSocket version") - .finish() + let mut res = Response::bad_request(); + res.head_mut().reason = Some("Unsupported WebSocket version"); + res } - HandshakeError::BadWebsocketKey => Response::build(StatusCode::BAD_REQUEST) - .reason("Handshake error") - .finish(), + HandshakeError::BadWebsocketKey => { + let mut res = Response::bad_request(); + res.head_mut().reason = Some("Handshake error"); + res + } } } } +impl From for Response { + fn from(err: HandshakeError) -> Self { + (&err).into() + } +} + /// Verify WebSocket handshake request and create handshake response. pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; @@ -213,7 +222,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { #[cfg(test)] mod tests { use super::*; - use crate::test::TestRequest; + use crate::{body::AnyBody, test::TestRequest}; use http::{header, Method}; #[test] @@ -327,18 +336,18 @@ mod tests { } #[test] - fn test_wserror_http_response() { - let resp = HandshakeError::GetMethodRequired.error_response(); + fn test_ws_error_http_response() { + let resp: Response = HandshakeError::GetMethodRequired.into(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp = HandshakeError::NoWebsocketUpgrade.error_response(); + let resp: Response = HandshakeError::NoWebsocketUpgrade.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp = HandshakeError::NoConnectionUpgrade.error_response(); + let resp: Response = HandshakeError::NoConnectionUpgrade.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp = HandshakeError::NoVersionHeader.error_response(); + let resp: Response = HandshakeError::NoVersionHeader.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp = HandshakeError::UnsupportedVersion.error_response(); + let resp: Response = HandshakeError::UnsupportedVersion.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp = HandshakeError::BadWebsocketKey.error_response(); + let resp: Response = HandshakeError::BadWebsocketKey.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 4bd7dbe14..414266d81 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,5 +1,7 @@ +use std::convert::Infallible; + use actix_http::{ - http, http::StatusCode, HttpMessage, HttpService, Request, Response, ResponseError, + body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response, }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; @@ -34,7 +36,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h1_v2() { let srv = test_server(move || { HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) + .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -62,7 +64,7 @@ async fn test_h1_v2() { async fn test_connection_close() { let srv = test_server(move || { HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) + .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() .map(|_| ()) }) @@ -76,11 +78,11 @@ async fn test_connection_close() { async fn test_with_query_parameter() { let srv = test_server(move || { HttpService::build() - .finish(|req: Request| { + .finish(|req: Request| async move { if req.uri().query().unwrap().contains("qp=") { - future::ok::<_, ()>(Response::ok()) + Ok::<_, Infallible>(Response::ok()) } else { - future::ok::<_, ()>(Response::bad_request()) + Ok(Response::bad_request()) } }) .tcp() @@ -97,9 +99,9 @@ async fn test_with_query_parameter() { #[display(fmt = "expect failed")] struct ExpectFailed; -impl ResponseError for ExpectFailed { - fn status_code(&self) -> StatusCode { - StatusCode::EXPECTATION_FAILED +impl From for Response { + fn from(_: ExpectFailed) -> Self { + Response::new(StatusCode::EXPECTATION_FAILED) } } @@ -123,7 +125,7 @@ async fn test_h1_expect() { let str = std::str::from_utf8(&buf).unwrap(); assert_eq!(str, "expect body"); - Ok::<_, ()>(Response::ok()) + Ok::<_, Infallible>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index d3a3bea3b..a58d0cc70 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -2,16 +2,16 @@ extern crate tls_openssl as openssl; -use std::io; +use std::{convert::Infallible, io}; use actix_http::{ - body::{Body, SizedStream}, + body::{AnyBody, Body, SizedStream}, error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Version, }, - Error, HttpMessage, HttpService, Request, Response, ResponseError, + Error, HttpMessage, HttpService, Request, Response, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -136,7 +136,7 @@ async fn test_h2_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - ok::<_, ()>(Response::new(statuses[idx])) + ok::<_, Infallible>(Response::new(statuses[idx])) }) .openssl(tls_config()) .map_err(|_| ()) @@ -206,7 +206,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - ok::<_, ()>(builder.body(data.clone())) + ok::<_, Infallible>(builder.body(data.clone())) }) .openssl(tls_config()) .map_err(|_| ()) @@ -246,7 +246,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -264,7 +264,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -288,7 +288,7 @@ async fn test_h2_head_empty() { async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -311,7 +311,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .openssl(tls_config()) .map_err(|_| ()) }) @@ -330,9 +330,12 @@ async fn test_h2_head_binary2() { async fn test_h2_body_length() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + .h2(|_| async { + let body = once(async { + Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) + }); + + Ok::<_, Infallible>( Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) @@ -355,7 +358,7 @@ async fn test_h2_body_chunked_explicit() { HttpService::build() .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), @@ -383,7 +386,7 @@ async fn test_h2_response_http_error_handling() { HttpService::build() .h2(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::CONTENT_TYPE, broken_header)) .body(STR), @@ -399,16 +402,19 @@ async fn test_h2_response_http_error_handling() { // read response let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + assert_eq!( + bytes, + Bytes::from_static(b"error processing HTTP: failed to parse header value") + ); } #[derive(Debug, Display, Error)] #[display(fmt = "error")] struct BadRequest; -impl ResponseError for BadRequest { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST +impl From for Response { + fn from(err: BadRequest) -> Self { + Response::build(StatusCode::BAD_REQUEST).body(err.to_string()) } } @@ -439,7 +445,7 @@ async fn test_h2_on_connect() { }) .h2(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::ok()) + ok::<_, Infallible>(Response::ok()) }) .openssl(tls_config()) .map_err(|_| ()) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index eec417541..cb7c77ad6 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -2,14 +2,21 @@ extern crate tls_rustls as rustls; +use std::{ + convert::Infallible, + io::{self, BufReader, Write}, + net::{SocketAddr, TcpStream as StdTcpStream}, + sync::Arc, +}; + use actix_http::{ - body::{Body, SizedStream}, + body::{AnyBody, Body, SizedStream}, error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Version, }, - Error, HttpService, Request, Response, ResponseError, + Error, HttpService, Request, Response, }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; @@ -24,12 +31,6 @@ use rustls::{ }; use webpki::DNSNameRef; -use std::{ - io::{self, BufReader, Write}, - net::{SocketAddr, TcpStream as StdTcpStream}, - sync::Arc, -}; - async fn load_body(mut stream: S) -> Result where S: Stream> + Unpin, @@ -173,7 +174,7 @@ async fn test_h2_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, Infallible>(Response::new(statuses[indx])) }) .rustls(tls_config()) }) @@ -242,7 +243,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - ok::<_, ()>(config.body(data.clone())) + ok::<_, Infallible>(config.body(data.clone())) }) .rustls(tls_config()) }).await; @@ -281,7 +282,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -298,7 +299,7 @@ async fn test_h2_body2() { async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -324,7 +325,7 @@ async fn test_h2_head_empty() { async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -349,7 +350,7 @@ async fn test_h2_head_binary() { async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() - .h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .rustls(tls_config()) }) .await; @@ -371,8 +372,8 @@ async fn test_h2_body_length() { let mut srv = test_server(move || { HttpService::build() .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + let body = once(ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))); + ok::<_, Infallible>( Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) @@ -394,7 +395,7 @@ async fn test_h2_body_chunked_explicit() { HttpService::build() .h2(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), @@ -420,9 +421,9 @@ async fn test_h2_response_http_error_handling() { let mut srv = test_server(move || { HttpService::build() .h2(fn_factory_with_config(|_: ()| { - ok::<_, ()>(fn_service(|_| { + ok::<_, Infallible>(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), @@ -438,16 +439,19 @@ async fn test_h2_response_http_error_handling() { // read response let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + assert_eq!( + bytes, + Bytes::from_static(b"error processing HTTP: failed to parse header value") + ); } #[derive(Debug, Display, Error)] #[display(fmt = "error")] struct BadRequest; -impl ResponseError for BadRequest { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST +impl From for Response { + fn from(_: BadRequest) -> Self { + Response::bad_request().set_body(AnyBody::from("error")) } } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index abfda249c..1e6d0b637 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -1,21 +1,25 @@ -use std::io::{Read, Write}; -use std::time::Duration; -use std::{net, thread}; +use std::{ + convert::Infallible, + io::{Read, Write}, + net, thread, + time::Duration, +}; use actix_http::{ - body::{Body, SizedStream}, - http::{self, header, StatusCode}, - Error, HttpService, KeepAlive, Request, Response, + body::{AnyBody, Body, SizedStream}, + header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response, + StatusCode, }; -use actix_http::{HttpMessage, ResponseError}; use actix_http_test::test_server; use actix_rt::time::sleep; use actix_service::fn_service; use actix_utils::future::{err, ok, ready}; use bytes::Bytes; use derive_more::{Display, Error}; -use futures_util::stream::{once, StreamExt as _}; -use futures_util::FutureExt as _; +use futures_util::{ + stream::{once, StreamExt as _}, + FutureExt as _, +}; use regex::Regex; #[actix_rt::test] @@ -27,7 +31,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - ok::<_, ()>(Response::ok()) + ok::<_, Infallible>(Response::ok()) }) .tcp() }) @@ -47,7 +51,7 @@ async fn test_h1_2() { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); - ok::<_, ()>(Response::ok()) + ok::<_, Infallible>(Response::ok()) }) .tcp() }) @@ -61,9 +65,9 @@ async fn test_h1_2() { #[display(fmt = "expect failed")] struct ExpectFailed; -impl ResponseError for ExpectFailed { - fn status_code(&self) -> StatusCode { - StatusCode::PRECONDITION_FAILED +impl From for Response { + fn from(_: ExpectFailed) -> Self { + Response::new(StatusCode::EXPECTATION_FAILED) } } @@ -78,7 +82,7 @@ async fn test_expect_continue() { err(ExpectFailed) } })) - .finish(|_| ok::<_, ()>(Response::ok())) + .finish(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -87,7 +91,7 @@ async fn test_expect_continue() { let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + assert!(data.starts_with("HTTP/1.1 417 Expectation Failed\r\ncontent-length")); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); @@ -109,7 +113,7 @@ async fn test_expect_continue_h1() { } }) })) - .h1(fn_service(|_| ok::<_, ()>(Response::ok()))) + .h1(fn_service(|_| ok::<_, Infallible>(Response::ok()))) .tcp() }) .await; @@ -118,7 +122,7 @@ async fn test_expect_continue_h1() { let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + assert!(data.starts_with("HTTP/1.1 417 Expectation Failed\r\ncontent-length")); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); @@ -190,7 +194,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| ok::<_, ()>(Response::ok())) + .finish(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -206,7 +210,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -222,7 +226,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -244,7 +248,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -265,7 +269,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -286,7 +290,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -306,7 +310,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -334,7 +338,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| ok::<_, ()>(Response::ok())) + .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; @@ -369,7 +373,7 @@ async fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, Infallible>(Response::new(statuses[indx])) }) .tcp() }) @@ -424,7 +428,7 @@ async fn test_h1_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", )); } - ok::<_, ()>(builder.body(data.clone())) + ok::<_, Infallible>(builder.body(data.clone())) }).tcp() }).await; @@ -462,7 +466,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h1_body() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -479,7 +483,7 @@ async fn test_h1_body() { async fn test_h1_head_empty() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -504,7 +508,7 @@ async fn test_h1_head_empty() { async fn test_h1_head_binary() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -529,7 +533,7 @@ async fn test_h1_head_binary() { async fn test_h1_head_binary2() { let srv = test_server(|| { HttpService::build() - .h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) + .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() }) .await; @@ -551,8 +555,8 @@ async fn test_h1_body_length() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + let body = once(ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))); + ok::<_, Infallible>( Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) @@ -574,7 +578,7 @@ async fn test_h1_body_chunked_explicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), @@ -609,7 +613,7 @@ async fn test_h1_body_chunked_implicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::build(StatusCode::OK).streaming(body)) + ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body)) }) .tcp() }) @@ -638,7 +642,7 @@ async fn test_h1_response_http_error_handling() { HttpService::build() .h1(fn_service(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( + ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), @@ -653,16 +657,19 @@ async fn test_h1_response_http_error_handling() { // read response let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + assert_eq!( + bytes, + Bytes::from_static(b"error processing HTTP: failed to parse header value") + ); } #[derive(Debug, Display, Error)] #[display(fmt = "error")] struct BadRequest; -impl ResponseError for BadRequest { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST +impl From for Response { + fn from(_: BadRequest) -> Self { + Response::bad_request().set_body(AnyBody::from("error")) } } @@ -692,7 +699,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - ok::<_, ()>(Response::ok()) + ok::<_, Infallible>(Response::ok()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index b17d4211f..6d0de2316 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,11 +1,12 @@ use std::{ cell::Cell, + convert::Infallible, task::{Context, Poll}, }; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{ - body::BodySize, + body::{AnyBody, BodySize}, h1, ws::{self, CloseCode, Frame, Item, Message}, Error, HttpService, Request, Response, @@ -13,6 +14,7 @@ use actix_http::{ use actix_http_test::test_server; use actix_service::{fn_factory, Service}; use bytes::Bytes; +use derive_more::{Display, Error, From}; use futures_core::future::LocalBoxFuture; use futures_util::{SinkExt as _, StreamExt as _}; @@ -33,12 +35,39 @@ impl WsService { } } +#[derive(Debug, Display, Error, From)] +enum WsServiceError { + #[display(fmt = "http error")] + Http(actix_http::Error), + + #[display(fmt = "ws handshake error")] + Ws(actix_http::ws::HandshakeError), + + #[display(fmt = "io error")] + Io(std::io::Error), + + #[display(fmt = "dispatcher error")] + Dispatcher, +} + +impl From for Response { + fn from(err: WsServiceError) -> Self { + match err { + WsServiceError::Http(err) => err.into(), + WsServiceError::Ws(err) => err.into(), + WsServiceError::Io(_err) => unreachable!(), + WsServiceError::Dispatcher => Response::internal_server_error() + .set_body(AnyBody::from(format!("{}", err))), + } + } +} + impl Service<(Request, Framed)> for WsService where T: AsyncRead + AsyncWrite + Unpin + 'static, { type Response = (); - type Error = Error; + type Error = WsServiceError; type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { @@ -56,7 +85,9 @@ where let framed = framed.replace_codec(ws::Codec::new()); - ws::Dispatcher::with(framed, service).await?; + ws::Dispatcher::with(framed, service) + .await + .map_err(|_| WsServiceError::Dispatcher)?; Ok(()) }) @@ -72,7 +103,7 @@ async fn service(msg: Frame) -> Result { Frame::Binary(bin) => Message::Binary(bin), Frame::Continuation(item) => Message::Continuation(item), Frame::Close(reason) => Message::Close(reason), - _ => return Err(Error::from(ws::ProtocolError::BadOpCode)), + _ => return Err(ws::ProtocolError::BadOpCode.into()), }; Ok(msg) @@ -82,8 +113,10 @@ async fn service(msg: Frame) -> Result { async fn test_simple() { let mut srv = test_server(|| { HttpService::build() - .upgrade(fn_factory(|| async { Ok::<_, ()>(WsService::new()) })) - .finish(|_| async { Ok::<_, ()>(Response::not_found()) }) + .upgrade(fn_factory(|| async { + Ok::<_, Infallible>(WsService::new()) + })) + .finish(|_| async { Ok::<_, Infallible>(Response::not_found()) }) .tcp() }) .await; diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 5d85c2687..c863af44a 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -31,7 +31,7 @@ extern crate tls_openssl as openssl; #[cfg(feature = "rustls")] extern crate tls_rustls as rustls; -use std::{fmt, net, sync::mpsc, thread, time}; +use std::{error::Error as StdError, fmt, net, sync::mpsc, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; @@ -39,7 +39,7 @@ use actix_http::{ http::{HeaderMap, Method}, ws, HttpService, Request, Response, }; -use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; +use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_web::{ dev::{AppConfig, MessageBody, Server, Service}, rt, web, Error, @@ -86,7 +86,7 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { start_with(TestServerConfig::default(), factory) } @@ -126,7 +126,7 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { let (tx, rx) = mpsc::channel(); @@ -153,25 +153,40 @@ where HttpVer::Http1 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h1(map_config(factory(), move |_| app_cfg.clone())) + .h1(map_config(fac, move |_| app_cfg.clone())) .tcp() }), HttpVer::Http2 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h2(map_config(factory(), move |_| app_cfg.clone())) + .h2(map_config(fac, move |_| app_cfg.clone())) .tcp() }), HttpVer::Both => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .finish(map_config(factory(), move |_| app_cfg.clone())) + .finish(map_config(fac, move |_| app_cfg.clone())) .tcp() }), }, @@ -180,25 +195,40 @@ where HttpVer::Http1 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h1(map_config(factory(), move |_| app_cfg.clone())) + .h1(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), HttpVer::Http2 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h2(map_config(factory(), move |_| app_cfg.clone())) + .h2(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), HttpVer::Both => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .finish(map_config(factory(), move |_| app_cfg.clone())) + .finish(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), }, @@ -207,25 +237,40 @@ where HttpVer::Http1 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h1(map_config(factory(), move |_| app_cfg.clone())) + .h1(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), HttpVer::Http2 => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .h2(map_config(factory(), move |_| app_cfg.clone())) + .h2(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), HttpVer::Both => builder.listen("test", tcp, move || { let app_cfg = AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + HttpService::build() .client_timeout(timeout) - .finish(map_config(factory(), move |_| app_cfg.clone())) + .finish(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), }, diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 8c575206d..f0a53d4e0 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -22,10 +22,11 @@ use actix_http::{ http::HeaderValue, ws::{hash_key, Codec}, }; -use actix_web::error::{Error, PayloadError}; -use actix_web::http::{header, Method, StatusCode}; -use actix_web::HttpResponseBuilder; -use actix_web::{HttpRequest, HttpResponse}; +use actix_web::{ + error::{Error, PayloadError}, + http::{header, Method, StatusCode}, + HttpRequest, HttpResponse, HttpResponseBuilder, +}; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::Stream; diff --git a/awc/examples/client.rs b/awc/examples/client.rs index b9574590d..234ee3ae4 100644 --- a/awc/examples/client.rs +++ b/awc/examples/client.rs @@ -1,7 +1,7 @@ -use actix_http::Error; +use std::error::Error as StdError; #[actix_web::main] -async fn main() -> Result<(), Error> { +async fn main() -> Result<(), Box> { std::env::set_var("RUST_LOG", "actix_http=trace"); env_logger::init(); diff --git a/awc/src/error.rs b/awc/src/error.rs index b715f6213..c83c5ebbf 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -6,7 +6,6 @@ pub use actix_http::http::Error as HttpError; pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; -use actix_http::ResponseError; use serde_json::error::Error as JsonError; use actix_http::http::{header::HeaderValue, StatusCode}; @@ -77,6 +76,3 @@ pub enum JsonPayloadError { } impl std::error::Error for JsonPayloadError {} - -/// Return `InternalServerError` for `JsonPayloadError` -impl ResponseError for JsonPayloadError {} diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 5fe8edb19..cb8c0f1bf 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -1,21 +1,21 @@ -use std::convert::TryFrom; -use std::net; -use std::rc::Rc; -use std::time::Duration; +use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration}; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; -use actix_http::body::Body; -use actix_http::http::header::IntoHeaderValue; -use actix_http::http::{Error as HttpError, HeaderMap, HeaderName, Method, Uri}; -use actix_http::{Error, RequestHead}; +use actix_http::{ + body::Body, + http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, + RequestHead, +}; -use crate::sender::{RequestSender, SendClientRequest}; -use crate::ClientConfig; +use crate::{ + sender::{RequestSender, SendClientRequest}, + ClientConfig, +}; -/// `FrozenClientRequest` struct represents clonable client request. +/// `FrozenClientRequest` struct represents cloneable client request. /// It could be used to send same request multiple times. #[derive(Clone)] pub struct FrozenClientRequest { @@ -82,7 +82,7 @@ impl FrozenClientRequest { pub fn send_stream(&self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into + 'static, + E: Into> + 'static, { RequestSender::Rc(self.head.clone(), None).send_stream( self.addr, @@ -207,7 +207,7 @@ impl FrozenSendBuilder { pub fn send_stream(self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into + 'static, + E: Into> + 'static, { if let Some(e) = self.err { return e.into(); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 562d6ee7f..122f3845c 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -128,8 +128,7 @@ pub use self::sender::SendClientRequest; /// An asynchronous HTTP and WebSocket client. /// -/// ## Examples -/// +/// # Examples /// ``` /// use awc::Client; /// diff --git a/awc/src/request.rs b/awc/src/request.rs index 483524102..c95cee839 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,25 +1,26 @@ -use std::convert::TryFrom; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, net}; +use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration}; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; -use actix_http::body::Body; -use actix_http::http::header::{self, IntoHeaderPair}; -use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, +use actix_http::{ + body::Body, + http::{ + header::{self, IntoHeaderPair}, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, + }, + RequestHead, }; -use actix_http::{Error, RequestHead}; #[cfg(feature = "cookies")] use crate::cookie::{Cookie, CookieJar}; -use crate::error::{FreezeRequestError, InvalidUrl}; -use crate::frozen::FrozenClientRequest; -use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; -use crate::ClientConfig; +use crate::{ + error::{FreezeRequestError, InvalidUrl}, + frozen::FrozenClientRequest, + sender::{PrepForSendingError, RequestSender, SendClientRequest}, + ClientConfig, +}; #[cfg(feature = "compress")] const HTTPS_ENCODING: &str = "br, gzip, deflate"; @@ -408,7 +409,7 @@ impl ClientRequest { pub fn send_stream(self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into + 'static, + E: Into> + 'static, { let slf = match self.prep_for_sending() { Ok(slf) => slf, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 0e63be221..7ac9c8ce9 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,6 +1,7 @@ use std::{ + error::Error as StdError, future::Future, - io, net, + net, pin::Pin, rc::Rc, task::{Context, Poll}, @@ -24,22 +25,30 @@ use serde::Serialize; #[cfg(feature = "compress")] use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; -use crate::connect::{ConnectRequest, ConnectResponse}; -use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; -use crate::response::ClientResponse; -use crate::ClientConfig; +use crate::{ + error::{FreezeRequestError, InvalidUrl, SendRequestError}, + ClientConfig, ClientResponse, ConnectRequest, ConnectResponse, +}; #[derive(Debug, From)] pub(crate) enum PrepForSendingError { Url(InvalidUrl), Http(HttpError), + Json(serde_json::Error), + Form(serde_urlencoded::ser::Error), } impl From for FreezeRequestError { fn from(err: PrepForSendingError) -> FreezeRequestError { match err { - PrepForSendingError::Url(e) => FreezeRequestError::Url(e), - PrepForSendingError::Http(e) => FreezeRequestError::Http(e), + PrepForSendingError::Url(err) => FreezeRequestError::Url(err), + PrepForSendingError::Http(err) => FreezeRequestError::Http(err), + PrepForSendingError::Json(err) => { + FreezeRequestError::Custom(Box::new(err), Box::new("json serialization error")) + } + PrepForSendingError::Form(err) => { + FreezeRequestError::Custom(Box::new(err), Box::new("form serialization error")) + } } } } @@ -49,6 +58,12 @@ impl From for SendRequestError { match err { PrepForSendingError::Url(e) => SendRequestError::Url(e), PrepForSendingError::Http(e) => SendRequestError::Http(e), + PrepForSendingError::Json(err) => { + SendRequestError::Custom(Box::new(err), Box::new("json serialization error")) + } + PrepForSendingError::Form(err) => { + SendRequestError::Custom(Box::new(err), Box::new("form serialization error")) + } } } } @@ -209,8 +224,7 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_json::to_string(value) { Ok(body) => body, - // TODO: own error type - Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), + Err(err) => return PrepForSendingError::Json(err).into(), }; if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { @@ -236,8 +250,7 @@ impl RequestSender { ) -> SendClientRequest { let body = match serde_urlencoded::to_string(value) { Ok(body) => body, - // TODO: own error type - Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(), + Err(err) => return PrepForSendingError::Form(err).into(), }; // set content-type @@ -266,7 +279,7 @@ impl RequestSender { ) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into + 'static, + E: Into> + 'static, { self.send_body( addr, diff --git a/src/data.rs b/src/data.rs index d63c15580..f09a88891 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,12 +1,13 @@ use std::{any::type_name, ops::Deref, sync::Arc}; -use actix_http::{error::Error, Extensions}; +use actix_http::Extensions; use actix_utils::future::{err, ok, Ready}; use futures_core::future::LocalBoxFuture; use serde::Serialize; use crate::{ dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, + Error, }; /// Data factory. diff --git a/src/error/error.rs b/src/error/error.rs new file mode 100644 index 000000000..add290867 --- /dev/null +++ b/src/error/error.rs @@ -0,0 +1,76 @@ +use std::{error::Error as StdError, fmt}; + +use actix_http::{body::AnyBody, Response}; + +use crate::{HttpResponse, ResponseError}; + +/// General purpose actix web error. +/// +/// An actix web error is used to carry errors from `std::error` +/// through actix in a convenient way. It can be created through +/// converting errors with `into()`. +/// +/// Whenever it is created from an external object a response error is created +/// for it that can be used to create an HTTP response from it this means that +/// if you have access to an actix `Error` you can always get a +/// `ResponseError` reference from it. +pub struct Error { + cause: Box, +} + +impl Error { + /// Returns the reference to the underlying `ResponseError`. + pub fn as_response_error(&self) -> &dyn ResponseError { + self.cause.as_ref() + } + + /// Similar to `as_response_error` but downcasts. + pub fn as_error(&self) -> Option<&T> { + ::downcast_ref(self.cause.as_ref()) + } + + /// Shortcut for creating an `HttpResponse`. + pub fn error_response(&self) -> HttpResponse { + self.cause.error_response() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.cause, f) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self.cause) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + // TODO: populate if replacement for Box is found + None + } +} + +impl From for Error { + fn from(val: std::convert::Infallible) -> Self { + match val {} + } +} + +/// `Error` for any error that implements `ResponseError` +impl From for Error { + fn from(err: T) -> Error { + Error { + cause: Box::new(err), + } + } +} + +impl From for Response { + fn from(err: Error) -> Response { + err.error_response().into() + } +} diff --git a/src/error/internal.rs b/src/error/internal.rs index 23b7dc31e..1d9ca904e 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, fmt, io::Write as _}; -use actix_http::{body::Body, header, Response, StatusCode}; +use actix_http::{body::Body, header, StatusCode}; use bytes::{BufMut as _, BytesMut}; -use crate::{Error, HttpResponse, ResponseError}; +use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError}; /// Wraps errors to alter the generated response status code. /// @@ -77,10 +77,10 @@ where } } - fn error_response(&self) -> Response { + fn error_response(&self) -> HttpResponse { match self.status { InternalErrorType::Status(status) => { - let mut res = Response::new(status); + let mut res = HttpResponse::new(status); let mut buf = BytesMut::new().writer(); let _ = write!(buf, "{}", self); @@ -88,20 +88,29 @@ where header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain; charset=utf-8"), ); - res.set_body(Body::from(buf.into_inner())).into() + res.set_body(Body::from(buf.into_inner())) } InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.borrow_mut().take() { - resp.into() + resp } else { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) + HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) } } } } } +impl Responder for InternalError +where + T: fmt::Debug + fmt::Display + 'static, +{ + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from_error(self) + } +} + macro_rules! error_helper { ($name:ident, $status:ident) => { paste::paste! { @@ -171,134 +180,134 @@ error_helper!( #[cfg(test)] mod tests { - use actix_http::{error::ParseError, Response}; + use actix_http::error::ParseError; use super::*; #[test] fn test_internal_error() { let err = InternalError::from_response(ParseError::Method, HttpResponse::Ok().finish()); - let resp: Response = err.error_response(); + let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_error_helpers() { - let res: Response = ErrorBadRequest("err").into(); + let res: HttpResponse = ErrorBadRequest("err").into(); assert_eq!(res.status(), StatusCode::BAD_REQUEST); - let res: Response = ErrorUnauthorized("err").into(); + let res: HttpResponse = ErrorUnauthorized("err").into(); assert_eq!(res.status(), StatusCode::UNAUTHORIZED); - let res: Response = ErrorPaymentRequired("err").into(); + let res: HttpResponse = ErrorPaymentRequired("err").into(); assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); - let res: Response = ErrorForbidden("err").into(); + let res: HttpResponse = ErrorForbidden("err").into(); assert_eq!(res.status(), StatusCode::FORBIDDEN); - let res: Response = ErrorNotFound("err").into(); + let res: HttpResponse = ErrorNotFound("err").into(); assert_eq!(res.status(), StatusCode::NOT_FOUND); - let res: Response = ErrorMethodNotAllowed("err").into(); + let res: HttpResponse = ErrorMethodNotAllowed("err").into(); assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); - let res: Response = ErrorNotAcceptable("err").into(); + let res: HttpResponse = ErrorNotAcceptable("err").into(); assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); - let res: Response = ErrorProxyAuthenticationRequired("err").into(); + let res: HttpResponse = ErrorProxyAuthenticationRequired("err").into(); assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - let res: Response = ErrorRequestTimeout("err").into(); + let res: HttpResponse = ErrorRequestTimeout("err").into(); assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); - let res: Response = ErrorConflict("err").into(); + let res: HttpResponse = ErrorConflict("err").into(); assert_eq!(res.status(), StatusCode::CONFLICT); - let res: Response = ErrorGone("err").into(); + let res: HttpResponse = ErrorGone("err").into(); assert_eq!(res.status(), StatusCode::GONE); - let res: Response = ErrorLengthRequired("err").into(); + let res: HttpResponse = ErrorLengthRequired("err").into(); assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); - let res: Response = ErrorPreconditionFailed("err").into(); + let res: HttpResponse = ErrorPreconditionFailed("err").into(); assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); - let res: Response = ErrorPayloadTooLarge("err").into(); + let res: HttpResponse = ErrorPayloadTooLarge("err").into(); assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - let res: Response = ErrorUriTooLong("err").into(); + let res: HttpResponse = ErrorUriTooLong("err").into(); assert_eq!(res.status(), StatusCode::URI_TOO_LONG); - let res: Response = ErrorUnsupportedMediaType("err").into(); + let res: HttpResponse = ErrorUnsupportedMediaType("err").into(); assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - let res: Response = ErrorRangeNotSatisfiable("err").into(); + let res: HttpResponse = ErrorRangeNotSatisfiable("err").into(); assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - let res: Response = ErrorExpectationFailed("err").into(); + let res: HttpResponse = ErrorExpectationFailed("err").into(); assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); - let res: Response = ErrorImATeapot("err").into(); + let res: HttpResponse = ErrorImATeapot("err").into(); assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); - let res: Response = ErrorMisdirectedRequest("err").into(); + let res: HttpResponse = ErrorMisdirectedRequest("err").into(); assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); - let res: Response = ErrorUnprocessableEntity("err").into(); + let res: HttpResponse = ErrorUnprocessableEntity("err").into(); assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); - let res: Response = ErrorLocked("err").into(); + let res: HttpResponse = ErrorLocked("err").into(); assert_eq!(res.status(), StatusCode::LOCKED); - let res: Response = ErrorFailedDependency("err").into(); + let res: HttpResponse = ErrorFailedDependency("err").into(); assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); - let res: Response = ErrorUpgradeRequired("err").into(); + let res: HttpResponse = ErrorUpgradeRequired("err").into(); assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); - let res: Response = ErrorPreconditionRequired("err").into(); + let res: HttpResponse = ErrorPreconditionRequired("err").into(); assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); - let res: Response = ErrorTooManyRequests("err").into(); + let res: HttpResponse = ErrorTooManyRequests("err").into(); assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); - let res: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + let res: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into(); assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - let res: Response = ErrorUnavailableForLegalReasons("err").into(); + let res: HttpResponse = ErrorUnavailableForLegalReasons("err").into(); assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - let res: Response = ErrorInternalServerError("err").into(); + let res: HttpResponse = ErrorInternalServerError("err").into(); assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); - let res: Response = ErrorNotImplemented("err").into(); + let res: HttpResponse = ErrorNotImplemented("err").into(); assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); - let res: Response = ErrorBadGateway("err").into(); + let res: HttpResponse = ErrorBadGateway("err").into(); assert_eq!(res.status(), StatusCode::BAD_GATEWAY); - let res: Response = ErrorServiceUnavailable("err").into(); + let res: HttpResponse = ErrorServiceUnavailable("err").into(); assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); - let res: Response = ErrorGatewayTimeout("err").into(); + let res: HttpResponse = ErrorGatewayTimeout("err").into(); assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); - let res: Response = ErrorHttpVersionNotSupported("err").into(); + let res: HttpResponse = ErrorHttpVersionNotSupported("err").into(); assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - let res: Response = ErrorVariantAlsoNegotiates("err").into(); + let res: HttpResponse = ErrorVariantAlsoNegotiates("err").into(); assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - let res: Response = ErrorInsufficientStorage("err").into(); + let res: HttpResponse = ErrorInsufficientStorage("err").into(); assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); - let res: Response = ErrorLoopDetected("err").into(); + let res: HttpResponse = ErrorLoopDetected("err").into(); assert_eq!(res.status(), StatusCode::LOOP_DETECTED); - let res: Response = ErrorNotExtended("err").into(); + let res: HttpResponse = ErrorNotExtended("err").into(); assert_eq!(res.status(), StatusCode::NOT_EXTENDED); - let res: Response = ErrorNetworkAuthenticationRequired("err").into(); + let res: HttpResponse = ErrorNetworkAuthenticationRequired("err").into(); assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } diff --git a/src/error/macros.rs b/src/error/macros.rs new file mode 100644 index 000000000..aeab74308 --- /dev/null +++ b/src/error/macros.rs @@ -0,0 +1,109 @@ +#[macro_export] +#[doc(hidden)] +macro_rules! __downcast_get_type_id { + () => { + /// A helper method to get the type ID of the type + /// this trait is implemented on. + /// This method is unsafe to *implement*, since `downcast_ref` relies + /// on the returned `TypeId` to perform a cast. + /// + /// Unfortunately, Rust has no notion of a trait method that is + /// unsafe to implement (marking it as `unsafe` makes it unsafe + /// to *call*). As a workaround, we require this method + /// to return a private type along with the `TypeId`. This + /// private type (`PrivateHelper`) has a private constructor, + /// making it impossible for safe code to construct outside of + /// this module. This ensures that safe code cannot violate + /// type-safety by implementing this method. + /// + /// We also take `PrivateHelper` as a parameter, to ensure that + /// safe code cannot obtain a `PrivateHelper` instance by + /// delegating to an existing implementation of `__private_get_type_id__` + #[doc(hidden)] + #[allow(dead_code)] + fn __private_get_type_id__(&self, _: PrivateHelper) -> (std::any::TypeId, PrivateHelper) + where + Self: 'static, + { + (std::any::TypeId::of::(), PrivateHelper(())) + } + }; +} + +//Generate implementation for dyn $name +#[doc(hidden)] +#[macro_export] +macro_rules! __downcast_dyn { + ($name:ident) => { + /// A struct with a private constructor, for use with + /// `__private_get_type_id__`. Its single field is private, + /// ensuring that it can only be constructed from this module + #[doc(hidden)] + #[allow(dead_code)] + pub struct PrivateHelper(()); + + impl dyn $name + 'static { + /// Downcasts generic body to a specific type. + #[allow(dead_code)] + pub fn downcast_ref(&self) -> Option<&T> { + if self.__private_get_type_id__(PrivateHelper(())).0 + == std::any::TypeId::of::() + { + // SAFETY: external crates cannot override the default + // implementation of `__private_get_type_id__`, since + // it requires returning a private type. We can therefore + // rely on the returned `TypeId`, which ensures that this + // case is correct. + unsafe { Some(&*(self as *const dyn $name as *const T)) } + } else { + None + } + } + + /// Downcasts a generic body to a mutable specific type. + #[allow(dead_code)] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if self.__private_get_type_id__(PrivateHelper(())).0 + == std::any::TypeId::of::() + { + // SAFETY: external crates cannot override the default + // implementation of `__private_get_type_id__`, since + // it requires returning a private type. We can therefore + // rely on the returned `TypeId`, which ensures that this + // case is correct. + unsafe { Some(&mut *(self as *const dyn $name as *const T as *mut T)) } + } else { + None + } + } + } + }; +} + +#[cfg(test)] +mod tests { + #![allow(clippy::upper_case_acronyms)] + + trait MB { + __downcast_get_type_id!(); + } + + __downcast_dyn!(MB); + + impl MB for String {} + impl MB for () {} + + #[actix_rt::test] + async fn test_any_casting() { + let mut body = String::from("hello cast"); + let resp_body: &mut dyn MB = &mut body; + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast"); + let body = &mut resp_body.downcast_mut::().unwrap(); + body.push('!'); + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast!"); + let not_body = resp_body.downcast_ref::<()>(); + assert!(not_body.is_none()); + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs index 146146c71..637d6ff16 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -9,14 +9,20 @@ use url::ParseError as UrlParseError; use crate::http::StatusCode; +#[allow(clippy::module_inception)] +mod error; mod internal; +mod macros; +mod response_error; +pub use self::error::Error; pub use self::internal::*; +pub use self::response_error::ResponseError; /// A convenience [`Result`](std::result::Result) for Actix Web operations. /// /// This type alias is generally used to avoid writing out `actix_http::Error` directly. -pub type Result = std::result::Result; +pub type Result = std::result::Result; /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, Error, From)] diff --git a/src/error/response_error.rs b/src/error/response_error.rs new file mode 100644 index 000000000..c58fff8be --- /dev/null +++ b/src/error/response_error.rs @@ -0,0 +1,144 @@ +//! `ResponseError` trait and foreign impls. + +use std::{ + error::Error as StdError, + fmt, + io::{self, Write as _}, +}; + +use actix_http::{body::AnyBody, header, Response, StatusCode}; +use bytes::BytesMut; + +use crate::{__downcast_dyn, __downcast_get_type_id}; +use crate::{helpers, HttpResponse}; + +/// Errors that can generate responses. +// TODO: add std::error::Error bound when replacement for Box is found +pub trait ResponseError: fmt::Debug + fmt::Display { + /// Returns appropriate status code for error. + /// + /// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is + /// also implemented and does not call `self.status_code()`, then this will not be used. + fn status_code(&self) -> StatusCode { + StatusCode::INTERNAL_SERVER_ERROR + } + + /// Creates full response for error. + /// + /// By default, the generated response uses a 500 Internal Server Error status code, a + /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl. + fn error_response(&self) -> HttpResponse { + let mut res = HttpResponse::new(self.status_code()); + + let mut buf = BytesMut::new(); + let _ = write!(helpers::MutWriter(&mut buf), "{}", self); + + res.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain; charset=utf-8"), + ); + + res.set_body(AnyBody::from(buf)) + } + + __downcast_get_type_id!(); +} + +__downcast_dyn!(ResponseError); + +impl ResponseError for Box {} + +#[cfg(feature = "openssl")] +impl ResponseError for actix_tls::accept::openssl::SslError {} + +impl ResponseError for serde::de::value::Error { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +impl ResponseError for std::str::Utf8Error { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +impl ResponseError for std::io::Error { + fn status_code(&self) -> StatusCode { + // TODO: decide if these errors should consider not found or permission errors + match self.kind() { + io::ErrorKind::NotFound => StatusCode::NOT_FOUND, + io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, + _ => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl ResponseError for actix_http::error::HttpError {} + +impl ResponseError for actix_http::Error { + fn status_code(&self) -> StatusCode { + // TODO: map error kinds to status code better + StatusCode::INTERNAL_SERVER_ERROR + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::new(self.status_code()).set_body(self.to_string().into()) + } +} + +impl ResponseError for actix_http::header::InvalidHeaderValue { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +impl ResponseError for actix_http::error::ParseError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +impl ResponseError for actix_http::error::BlockingError {} + +impl ResponseError for actix_http::error::PayloadError { + fn status_code(&self) -> StatusCode { + match *self { + actix_http::error::PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, + } + } +} + +impl ResponseError for actix_http::ws::ProtocolError {} + +impl ResponseError for actix_http::error::ContentTypeError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +impl ResponseError for actix_http::ws::HandshakeError { + fn error_response(&self) -> HttpResponse { + Response::from(self).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error_casting() { + use actix_http::error::{ContentTypeError, PayloadError}; + + let err = PayloadError::Overflow; + let resp_err: &dyn ResponseError = &err; + + let err = resp_err.downcast_ref::().unwrap(); + assert_eq!(err.to_string(), "Payload reached size limit."); + + let not_err = resp_err.downcast_ref::(); + assert!(not_err.is_none()); + } +} diff --git a/src/extract.rs b/src/extract.rs index 80f2384a0..45cb330a3 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -47,8 +47,7 @@ pub trait FromRequest: Sized { /// /// If the FromRequest for T fails, return None rather than returning an error response /// -/// ## Example -/// +/// # Examples /// ``` /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; @@ -139,8 +138,7 @@ where /// /// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response /// -/// ## Example -/// +/// # Examples /// ``` /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; diff --git a/src/handler.rs b/src/handler.rs index 822dcafdd..bc91ce41b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -3,18 +3,14 @@ use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use actix_http::Error; use actix_service::{Service, ServiceFactory}; use actix_utils::future::{ready, Ready}; use futures_core::ready; use pin_project::pin_project; use crate::{ - extract::FromRequest, - request::HttpRequest, - responder::Responder, - response::HttpResponse, service::{ServiceRequest, ServiceResponse}, + Error, FromRequest, HttpRequest, HttpResponse, Responder, }; /// A request handler is an async function that accepts zero or more parameters that can be diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 000000000..1d2679fce --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,25 @@ +use std::io; + +use bytes::BufMut; + +/// An `io::Write`r that only requires mutable reference and assumes that there is space available +/// in the buffer for every write operation or that it can be extended implicitly (like +/// `bytes::BytesMut`, for example). +/// +/// This is slightly faster (~10%) than `bytes::buf::Writer` in such cases because it does not +/// perform a remaining length check before writing. +pub(crate) struct MutWriter<'a, B>(pub(crate) &'a mut B); + +impl<'a, B> io::Write for MutWriter<'a, B> +where + B: BufMut, +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.put_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4e8093a2a..b488b962b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ //! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. //! -//! ## Example -//! +//! # Examples //! ```no_run //! use actix_web::{get, web, App, HttpServer, Responder}; //! @@ -20,8 +19,7 @@ //! } //! ``` //! -//! ## Documentation & Community Resources -//! +//! # Documentation & Community Resources //! In addition to this API documentation, several other resources are available: //! //! * [Website & User Guide](https://actix.rs/) @@ -44,8 +42,7 @@ //! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! and otherwise utilizing them. //! -//! ## Features -//! +//! # Features //! * Supports *HTTP/1.x* and *HTTP/2* //! * Streaming and pipelining //! * Keep-alive and slow requests handling @@ -59,8 +56,7 @@ //! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Runs on stable Rust 1.46+ //! -//! ## Crate Features -//! +//! # Crate Features //! * `compress` - content encoding compression support (enabled by default) //! * `cookies` - cookies support (enabled by default) //! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` @@ -80,6 +76,7 @@ pub mod error; mod extract; pub mod guard; mod handler; +mod helpers; pub mod http; mod info; pub mod middleware; @@ -98,7 +95,7 @@ pub(crate) mod types; pub mod web; pub use actix_http::Response as BaseHttpResponse; -pub use actix_http::{body, Error, HttpMessage, ResponseError}; +pub use actix_http::{body, HttpMessage}; #[doc(inline)] pub use actix_rt as rt; pub use actix_web_codegen::*; @@ -106,7 +103,7 @@ pub use actix_web_codegen::*; pub use cookie; pub use crate::app::App; -pub use crate::error::Result; +pub use crate::error::{Error, ResponseError, Result}; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; @@ -140,7 +137,9 @@ pub mod dev { pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; - pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; + pub use actix_http::body::{ + AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream, + }; #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 4f2f2a504..95f5f4b52 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -50,7 +50,7 @@ where T: Transform, T::Future: 'static, T::Response: MapServiceResponseBody, - Error: From, + T::Error: Into, { type Response = ServiceResponse; type Error = Error; @@ -75,7 +75,7 @@ impl Service for CompatMiddleware where S: Service, S::Response: MapServiceResponseBody, - Error: From, + S::Error: Into, { type Response = ServiceResponse; type Error = Error; @@ -99,12 +99,16 @@ impl Future for CompatMiddlewareFuture where Fut: Future>, T: MapServiceResponseBody, - Error: From, + E: Into, { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let res = ready!(self.project().fut.poll(cx))?; + let res = match ready!(self.project().fut.poll(cx)) { + Ok(res) => res, + Err(err) => return Poll::Ready(Err(err.into())), + }; + Poll::Ready(Ok(res.map_body())) } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index f8514c7cc..0eb4d0a83 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -13,7 +13,6 @@ use actix_http::{ body::{MessageBody, ResponseBody}, encoding::Encoder, http::header::{ContentEncoding, ACCEPT_ENCODING}, - Error, }; use actix_service::{Service, Transform}; use actix_utils::future::{ok, Ready}; @@ -23,6 +22,7 @@ use pin_project::pin_project; use crate::{ dev::BodyEncoding, service::{ServiceRequest, ServiceResponse}, + Error, }; /// Middleware for compressing response payloads. diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 88834f8ce..75cc819bc 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -13,8 +13,8 @@ use futures_core::{future::LocalBoxFuture, ready}; use crate::{ dev::{ServiceRequest, ServiceResponse}, - error::{Error, Result}, http::StatusCode, + Error, Result, }; /// Return type for [`ErrorHandlers`] custom handlers. diff --git a/src/request.rs b/src/request.rs index e3da991de..42c722c46 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,7 +7,7 @@ use std::{ use actix_http::{ http::{HeaderMap, Method, Uri, Version}, - Error, Extensions, HttpMessage, Message, Payload, RequestHead, + Extensions, HttpMessage, Message, Payload, RequestHead, }; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; @@ -17,7 +17,7 @@ use smallvec::SmallVec; use crate::{ app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, - extract::FromRequest, info::ConnectionInfo, rmap::ResourceMap, + info::ConnectionInfo, rmap::ResourceMap, Error, FromRequest, }; #[cfg(feature = "cookies")] @@ -356,8 +356,7 @@ impl Drop for HttpRequest { /// It is possible to get `HttpRequest` as an extractor handler parameter /// -/// ## Example -/// +/// # Examples /// ``` /// use actix_web::{web, App, HttpRequest}; /// use serde_derive::Deserialize; diff --git a/src/request_data.rs b/src/request_data.rs index 559d6ecbf..581943015 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -1,9 +1,8 @@ use std::{any::type_name, ops::Deref}; -use actix_http::error::Error; use actix_utils::future::{err, ok, Ready}; -use crate::{dev::Payload, error::ErrorInternalServerError, FromRequest, HttpRequest}; +use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpRequest}; /// Request-local data extractor. /// diff --git a/src/resource.rs b/src/resource.rs index 049e56291..8c2b83b60 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -3,7 +3,7 @@ use std::fmt; use std::future::Future; use std::rc::Rc; -use actix_http::{Error, Extensions}; +use actix_http::Extensions; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ @@ -13,14 +13,16 @@ use actix_service::{ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; -use crate::extract::FromRequest; -use crate::guard::Guard; -use crate::handler::Handler; -use crate::responder::Responder; -use crate::route::{Route, RouteService}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{data::Data, HttpResponse}; +use crate::{ + data::Data, + dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}, + guard::Guard, + handler::Handler, + responder::Responder, + route::{Route, RouteService}, + service::{ServiceRequest, ServiceResponse}, + Error, FromRequest, HttpResponse, +}; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; diff --git a/src/responder.rs b/src/responder.rs index 8bf8d9ea0..c5852a501 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, fmt}; +use std::borrow::Cow; use actix_http::{ body::Body, @@ -6,7 +6,7 @@ use actix_http::{ }; use bytes::{Bytes, BytesMut}; -use crate::{error::InternalError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// Trait implemented by types that can be converted to an HTTP response. /// @@ -226,15 +226,6 @@ impl Responder for CustomResponder { } } -impl Responder for InternalError -where - T: fmt::Debug + fmt::Display + 'static, -{ - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::from_error(self.into()) - } -} - #[cfg(test)] pub(crate) mod tests { use actix_service::Service; diff --git a/src/response/builder.rs b/src/response/builder.rs index b9a10c56b..6e013cae2 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -1,13 +1,14 @@ use std::{ cell::{Ref, RefMut}, convert::TryInto, + error::Error as StdError, future::Future, pin::Pin, task::{Context, Poll}, }; use actix_http::{ - body::{Body, BodyStream}, + body::{AnyBody, BodyStream}, http::{ header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, ConnectionType, Error as HttpError, StatusCode, @@ -32,7 +33,7 @@ use crate::{ /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct HttpResponseBuilder { - res: Option>, + res: Option>, err: Option, #[cfg(feature = "cookies")] cookies: Option, @@ -310,7 +311,7 @@ impl HttpResponseBuilder { /// /// `HttpResponseBuilder` can not be used after this call. #[inline] - pub fn body>(&mut self, body: B) -> HttpResponse { + pub fn body>(&mut self, body: B) -> HttpResponse { match self.message_body(body.into()) { Ok(res) => res, Err(err) => HttpResponse::from_error(err), @@ -354,9 +355,9 @@ impl HttpResponseBuilder { pub fn streaming(&mut self, stream: S) -> HttpResponse where S: Stream> + Unpin + 'static, - E: Into + 'static, + E: Into> + 'static, { - self.body(Body::from_message(BodyStream::new(stream))) + self.body(AnyBody::from_message(BodyStream::new(stream))) } /// Set a json body and generate `Response` @@ -375,9 +376,9 @@ impl HttpResponseBuilder { self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); } - self.body(Body::from(body)) + self.body(AnyBody::from(body)) } - Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)), } } @@ -386,7 +387,7 @@ impl HttpResponseBuilder { /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn finish(&mut self) -> HttpResponse { - self.body(Body::Empty) + self.body(AnyBody::Empty) } /// This method construct new `HttpResponseBuilder` @@ -415,7 +416,7 @@ impl From for HttpResponse { } } -impl From for Response { +impl From for Response { fn from(mut builder: HttpResponseBuilder) -> Self { builder.finish().into() } diff --git a/src/response/response.rs b/src/response/response.rs index 194e2dff8..9dd804be0 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::{Body, MessageBody}, + body::{AnyBody, Body, MessageBody}, http::{header::HeaderMap, StatusCode}, Extensions, Response, ResponseHead, }; @@ -25,12 +25,12 @@ use { use crate::{error::Error, HttpResponseBuilder}; /// An HTTP Response -pub struct HttpResponse { +pub struct HttpResponse { res: Response, pub(crate) error: Option, } -impl HttpResponse { +impl HttpResponse { /// Create HTTP response builder with specific status. #[inline] pub fn build(status: StatusCode) -> HttpResponseBuilder { @@ -48,13 +48,8 @@ impl HttpResponse { /// Create an error response. #[inline] - pub fn from_error(error: Error) -> Self { - let res = error.as_response_error().error_response(); - - Self { - res, - error: Some(error), - } + pub fn from_error(error: impl Into) -> Self { + error.into().as_response_error().error_response() } } @@ -238,7 +233,6 @@ impl HttpResponse { impl fmt::Debug for HttpResponse where B: MessageBody, - B::Error: Into, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HttpResponse") diff --git a/src/route.rs b/src/route.rs index 0a297b456..44f7e30b8 100644 --- a/src/route.rs +++ b/src/route.rs @@ -2,19 +2,19 @@ use std::{future::Future, rc::Rc}; -use actix_http::{http::Method, Error}; +use actix_http::http::Method; use actix_service::{ boxed::{self, BoxService, BoxServiceFactory}, Service, ServiceFactory, }; use futures_core::future::LocalBoxFuture; -use crate::extract::FromRequest; -use crate::guard::{self, Guard}; -use crate::handler::{Handler, HandlerService}; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; +use crate::{ + guard::{self, Guard}, + handler::{Handler, HandlerService}, + service::{ServiceRequest, ServiceResponse}, + Error, FromRequest, HttpResponse, Responder, +}; /// Resource route definition /// @@ -188,7 +188,7 @@ impl Route { #[cfg(test)] mod tests { - use std::time::Duration; + use std::{convert::Infallible, time::Duration}; use actix_rt::time::sleep; use bytes::Bytes; @@ -215,7 +215,7 @@ mod tests { })) .route(web::post().to(|| async { sleep(Duration::from_millis(100)).await; - Ok::<_, ()>(HttpResponse::Created()) + Ok::<_, Infallible>(HttpResponse::Created()) })) .route(web::delete().to(|| async { sleep(Duration::from_millis(100)).await; diff --git a/src/server.rs b/src/server.rs index 80e300b9a..89328215d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,23 +1,25 @@ use std::{ any::Any, - cmp, fmt, io, + cmp, + error::Error as StdError, + fmt, io, marker::PhantomData, net, sync::{Arc, Mutex}, }; -use actix_http::{ - body::MessageBody, Error, Extensions, HttpService, KeepAlive, Request, Response, -}; +use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response}; use actix_server::{Server, ServerBuilder}; -use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; +use actix_service::{ + map_config, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, +}; #[cfg(feature = "openssl")] use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig; -use crate::config::AppConfig; +use crate::{config::AppConfig, Error}; struct Socket { scheme: &'static str, @@ -81,7 +83,7 @@ where S::Service: 'static, // S::Service: 'static, B: MessageBody + 'static, - B::Error: Into, + B::Error: Into>, { /// Create new HTTP server with application factory pub fn new(factory: F) -> Self { @@ -301,7 +303,11 @@ where svc }; - svc.finish(map_config(factory(), move |_| { + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| { AppConfig::new(false, host.clone(), addr) })) .tcp() @@ -356,7 +362,11 @@ where svc }; - svc.finish(map_config(factory(), move |_| { + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) .openssl(acceptor.clone()) @@ -410,7 +420,11 @@ where svc }; - svc.finish(map_config(factory(), move |_| { + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) .rustls(config.clone()) @@ -533,7 +547,11 @@ where svc }; - svc.finish(map_config(factory(), move |_| config.clone())) + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| config.clone())) }) })?; Ok(self) @@ -568,14 +586,20 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), socket_addr, ); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), + .finish(map_config(fac, move |_| config.clone())), ) }, )?; + Ok(self) } } diff --git a/src/service.rs b/src/service.rs index b7f244797..2956fe6cb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,24 +2,24 @@ use std::cell::{Ref, RefMut}; use std::rc::Rc; use std::{fmt, net}; -use actix_http::body::{Body, MessageBody}; -use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, + body::{AnyBody, MessageBody}, + http::{HeaderMap, Method, StatusCode, Uri, Version}, + Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; #[cfg(feature = "cookies")] use cookie::{Cookie, ParseError as CookieParseError}; -use crate::dev::insert_slash; -use crate::guard::Guard; -use crate::info::ConnectionInfo; -use crate::request::HttpRequest; -use crate::rmap::ResourceMap; use crate::{ config::{AppConfig, AppService}, - HttpResponse, + dev::insert_slash, + guard::Guard, + info::ConnectionInfo, + request::HttpRequest, + rmap::ResourceMap, + Error, HttpResponse, }; pub trait HttpServiceFactory { @@ -330,15 +330,15 @@ impl fmt::Debug for ServiceRequest { } } -pub struct ServiceResponse { +pub struct ServiceResponse { request: HttpRequest, response: HttpResponse, } -impl ServiceResponse { +impl ServiceResponse { /// Create service response from the error pub fn from_err>(err: E, request: HttpRequest) -> Self { - let response = HttpResponse::from_error(err.into()); + let response = HttpResponse::from_error(err); ServiceResponse { request, response } } } diff --git a/src/types/form.rs b/src/types/form.rs index d1deac937..ce85983d1 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -188,7 +188,7 @@ impl Responder for Form { Ok(body) => HttpResponse::Ok() .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .body(body), - Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err).into()), + Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)), } } } diff --git a/src/types/json.rs b/src/types/json.rs index 24abcecea..44b548355 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -127,7 +127,7 @@ impl Responder for Json { Ok(body) => HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) .body(body), - Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)), } } } @@ -500,7 +500,7 @@ mod tests { }; let resp = HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp.into()).into() + InternalError::from_response(err, resp).into() })) .to_http_parts(); diff --git a/src/types/path.rs b/src/types/path.rs index 59a107a7e..9dab79414 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -2,14 +2,13 @@ use std::{fmt, ops, sync::Arc}; -use actix_http::error::Error; use actix_router::PathDeserializer; use actix_utils::future::{ready, Ready}; use serde::de; use crate::{ dev::Payload, - error::{ErrorNotFound, PathError}, + error::{Error, ErrorNotFound, PathError}, FromRequest, HttpRequest, }; @@ -296,11 +295,8 @@ mod tests { async fn test_custom_err_handler() { let (req, mut pl) = TestRequest::with_uri("/name/user1/") .app_data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish().into(), - ) - .into() + error::InternalError::from_response(err, HttpResponse::Conflict().finish()) + .into() })) .to_http_parts(); diff --git a/src/types/query.rs b/src/types/query.rs index 978d00b5f..613a438d3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -267,7 +267,7 @@ mod tests { let req = TestRequest::with_uri("/name/user1/") .app_data(QueryConfig::default().error_handler(|e, _| { let resp = HttpResponse::UnprocessableEntity().finish(); - InternalError::from_response(e, resp.into()).into() + InternalError::from_response(e, resp).into() })) .to_srv_request(); From c260fb1c486f9db1ad50d48e9e7ceb6b4bd8a31f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 19 Jun 2021 11:51:20 +0100 Subject: [PATCH 013/861] beta.7 releases (#2266) --- CHANGES.md | 3 +++ Cargo.toml | 6 +++--- README.md | 4 ++-- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 4 ++-- actix-http-test/Cargo.toml | 6 +++--- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 6 +++--- actix-multipart/README.md | 4 ++-- actix-test/Cargo.toml | 6 +++--- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 8 ++++---- actix-web-actors/README.md | 4 ++-- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- actix-web-codegen/README.md | 4 ++-- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 8 ++++---- awc/README.md | 4 ++-- 23 files changed, 66 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5d33e6dd1..ceaa4ffe4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.7 - 2021-06-17 ### Added * `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] diff --git a/Cargo.toml b/Cargo.toml index bd4cdd91f..11a173714 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.6" +version = "4.0.0-beta.7" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -67,7 +67,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.6" +actix-http = "3.0.0-beta.7" ahash = "0.7" bytes = "1" @@ -95,7 +95,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.5", features = ["openssl"] } +awc = { version = "3.0.0-beta.6", features = ["openssl"] } brotli2 = "0.3.2" criterion = "0.3" diff --git a/README.md b/README.md index 60ec57c60..c6ef6d868 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web/4.0.0-beta.5) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web/4.0.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.5) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.7)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index bd4030e6e..9a643f127 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.5 - 2021-06-17 * `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] * For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] * `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6cff9b263..44c29dc92 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.4" +version = "0.6.0-beta.5" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -17,8 +17,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.6", default-features = false } -actix-http = "3.0.0-beta.6" +actix-web = { version = "4.0.0-beta.7", default-features = false } +actix-http = "3.0.0-beta.7" actix-service = "2.0.0" actix-utils = "3.0.0" @@ -35,5 +35,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.6" +actix-web = "4.0.0-beta.7" actix-test = "0.1.0-beta.2" diff --git a/actix-files/README.md b/actix-files/README.md index 895d5e687..524f5c38e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.4)](https://docs.rs/actix-files/0.6.0-beta.4) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.5)](https://docs.rs/actix-files/0.6.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.4/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.5/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 88b4bd04a..5d797aaa9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.5", default-features = false } +awc = { version = "3.0.0-beta.6", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.6" +actix-web = { version = "4.0.0-beta.7", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.7" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f25e14254..16a650d90 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.7 - 2021-06-17 ### Added * Alias `body::Body` as `body::AnyBody`. [#2215] * `BoxAnyBody`: a boxed message body with boxed errors. [#2183] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ea338fec1..bfb51885f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.6" +version = "3.0.0-beta.7" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index 87eb38e5d..5271d8738 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http/3.0.0-beta.6) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.7)](https://docs.rs/actix-http/3.0.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.6) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.7) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index cd50305cb..0b6affa3c 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.5 - 2021-06-17 +* No notable changes. + + ## 0.4.0-beta.4 - 2021-04-02 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index fd9a8d529..41b0fbae7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.4" +version = "0.4.0-beta.5" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" readme = "README.md" @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.6", default-features = false } +actix-web = { version = "4.0.0-beta.7", default-features = false } actix-utils = "3.0.0" bytes = "1" @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.6" +actix-http = "3.0.0-beta.7" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 8a4279a62..f6d008fc3 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.4)](https://docs.rs/actix-multipart/0.4.0-beta.4) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.5)](https://docs.rs/actix-multipart/0.4.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.4/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) ## Documentation & Resources diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 23f3650cd..607038377 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,13 +20,13 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0" -actix-http = "3.0.0-beta.6" +actix-http = "3.0.0-beta.7" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.7", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.5", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.6", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index cd76b201e..a7ee7a9e1 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.5 - 2021-06-17 +* No notable changes. + + ## 4.0.0-beta.4 - 2021-04-02 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index b653dd92a..159b10d58 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.4" +version = "4.0.0-beta.5" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" @@ -18,8 +18,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0" -actix-http = "3.0.0-beta.6" -actix-web = { version = "4.0.0-beta.6", default-features = false } +actix-http = "3.0.0-beta.7" +actix-web = { version = "4.0.0-beta.7", default-features = false } bytes = "1" bytestring = "1" @@ -31,6 +31,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.2" -awc = { version = "3.0.0-beta.5", default-features = false } +awc = { version = "3.0.0-beta.6", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 2dc779f10..0d926f5ee 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web-actors/4.0.0-beta.4) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web-actors/4.0.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index f2c9b44b5..a8a901f72 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.5.0-beta.3 - 2021-06-17 +* No notable changes. + + ## 0.5.0-beta.2 - 2021-03-09 * Preserve doc comments when using route macros. [#2022] * Add `name` attribute to `route` macro. [#1934] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index db4f8430c..29565f74a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.2" +version = "0.5.0-beta.3" description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" @@ -22,7 +22,7 @@ proc-macro2 = "1" actix-rt = "2.2" actix-test = "0.1.0-beta.2" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.6" +actix-web = "4.0.0-beta.7" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 9552d4b56..ef3aa72df 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.2)](https://docs.rs/actix-web-codegen/0.5.0-beta.2) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.3)](https://docs.rs/actix-web-codegen/0.5.0-beta.3) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.2/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.2) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b2e0ff78d..c66c6cda8 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.6 - 2021-06-17 +* No significant changes since 3.0.0-beta.5. + + ## 3.0.0-beta.5 - 2021-04-17 ### Removed * Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c8a184513..645f70101 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.5" +version = "3.0.0-beta.6" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -47,7 +47,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" -actix-http = "3.0.0-beta.6" +actix-http = "3.0.0-beta.7" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -68,8 +68,8 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.6", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.6", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.7", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.7", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" diff --git a/awc/README.md b/awc/README.md index a836a2497..5076c59a4 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.4)](https://docs.rs/awc/3.0.0-beta.4) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.6)](https://docs.rs/awc/3.0.0-beta.6) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.4/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.4) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.6/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.6) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources From baa5a663c47343459f493280ec31eff4c255280d Mon Sep 17 00:00:00 2001 From: Arthur Le Moigne Date: Sat, 19 Jun 2021 21:21:13 +0200 Subject: [PATCH 014/861] Select compression algorithm using features flags (#2250) Add compress-* feature flags in actix-http / actix-web / awc. This allow enable / disable not wanted compression algorithm. --- CHANGES.md | 5 ++++ Cargo.toml | 23 +++++++++++----- MIGRATION.md | 12 ++++++++ actix-http/CHANGES.md | 5 ++++ actix-http/Cargo.toml | 10 +++++-- actix-http/src/encoding/decoder.rs | 26 ++++++++++++++++-- actix-http/src/encoding/encoder.rs | 30 ++++++++++++++++++-- actix-http/src/lib.rs | 17 +++++++----- awc/CHANGES.md | 6 +++- awc/Cargo.toml | 17 +++++++++--- awc/src/request.rs | 44 ++++++++++++++++++------------ awc/src/sender.rs | 6 ++-- src/http/header/encoding.rs | 6 +++- src/lib.rs | 5 ++-- src/middleware/compat.rs | 4 +-- src/middleware/mod.rs | 5 ++-- src/types/form.rs | 19 ++++++++----- src/types/json.rs | 19 ++++++++----- src/types/payload.rs | 17 ++++++++---- 19 files changed, 204 insertions(+), 72 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ceaa4ffe4..83a683eb6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ ## Unreleased - 2021-xx-xx +### Changed + +* Change compression algorithm features flags. [#2250] + +[#2250]: https://github.com/actix/actix-web/pull/2250 ## 4.0.0-beta.7 - 2021-06-17 ### Added diff --git a/Cargo.toml b/Cargo.toml index 11a173714..770c9a050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] +features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] [lib] name = "actix_web" @@ -39,10 +39,14 @@ members = [ # resolver = "2" [features] -default = ["compress", "cookies"] +default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] -# content-encoding support -compress = ["actix-http/compress"] +# Brotli algorithm content-encoding support +compress-brotli = ["actix-http/compress-brotli", "__compress"] +# Gzip and deflate algorithms content-encoding support +compress-gzip = ["actix-http/compress-gzip", "__compress"] +# Zstd algorithm content-encoding support +compress-zstd = ["actix-http/compress-zstd", "__compress"] # support for cookies cookies = ["cookie"] @@ -56,6 +60,10 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] +# Internal (PRIVATE!) features used to aid testing and cheking feature status. +# Don't rely on these whatsoever. They may disappear at anytime. +__compress = [] + [dependencies] actix-codec = "0.4.0" actix-macros = "0.2.1" @@ -71,6 +79,7 @@ actix-http = "3.0.0-beta.7" ahash = "0.7" bytes = "1" +cfg-if = "1" cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" either = "1.5.3" @@ -126,15 +135,15 @@ awc = { path = "awc" } [[test]] name = "test_server" -required-features = ["compress", "cookies"] +required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] [[example]] name = "basic" -required-features = ["compress"] +required-features = ["compress-gzip"] [[example]] name = "uds" -required-features = ["compress"] +required-features = ["compress-gzip"] [[example]] name = "on_connect" diff --git a/MIGRATION.md b/MIGRATION.md index e01702868..9c29b8db9 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -10,6 +10,18 @@ Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. +* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). + By default all compression algorithms are enabled. + To select algorithm you want to include with `middleware::Compress` use following flags: + - `compress-brotli` + - `compress-gzip` + - `compress-zstd` + If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want + to have compression enabled. Please change features selection like bellow: + + Before: `"compress"` + After: `"compress-brotli", "compress-gzip", "compress-zstd"` + ## 3.0.0 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 16a650d90..83f3a0527 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,11 @@ ## Unreleased - 2021-xx-xx +### Changed + +* Change compression algorithm features flags. [#2250] + +[#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.7 - 2021-06-17 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index bfb51885f..35ea89862 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress"] +features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] [lib] name = "actix_http" @@ -32,11 +32,17 @@ openssl = ["actix-tls/openssl"] rustls = ["actix-tls/rustls"] # enable compression support -compress = ["flate2", "brotli2", "zstd"] +compress-brotli = ["brotli2", "__compress"] +compress-gzip = ["flate2", "__compress"] +compress-zstd = ["zstd", "__compress"] # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] +# Internal (PRIVATE!) features used to aid testing and cheking feature status. +# Don't rely on these whatsoever. They may disappear at anytime. +__compress = [] + [dependencies] actix-service = "2.0.0" actix-codec = "0.4.0" diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 58981e82e..d3e304836 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -8,10 +8,16 @@ use std::{ }; use actix_rt::task::{spawn_blocking, JoinHandle}; -use brotli2::write::BrotliDecoder; use bytes::Bytes; -use flate2::write::{GzDecoder, ZlibDecoder}; use futures_core::{ready, Stream}; + +#[cfg(feature = "compress-brotli")] +use brotli2::write::BrotliDecoder; + +#[cfg(feature = "compress-gzip")] +use flate2::write::{GzDecoder, ZlibDecoder}; + +#[cfg(feature = "compress-zstd")] use zstd::stream::write::Decoder as ZstdDecoder; use crate::{ @@ -37,15 +43,19 @@ where #[inline] pub fn new(stream: S, encoding: ContentEncoding) -> Decoder { let decoder = match encoding { + #[cfg(feature = "compress-brotli")] ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( BrotliDecoder::new(Writer::new()), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ZlibDecoder::new(Writer::new()), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( GzDecoder::new(Writer::new()), ))), + #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new( ZstdDecoder::new(Writer::new()).expect( "Failed to create zstd decoder. This is a bug. \ @@ -148,17 +158,22 @@ where } enum ContentDecoder { + #[cfg(feature = "compress-gzip")] Deflate(Box>), + #[cfg(feature = "compress-gzip")] Gzip(Box>), + #[cfg(feature = "compress-brotli")] Br(Box>), // We need explicit 'static lifetime here because ZstdDecoder need lifetime // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` + #[cfg(feature = "compress-zstd")] Zstd(Box>), } impl ContentDecoder { fn feed_eof(&mut self) -> io::Result> { match self { + #[cfg(feature = "compress-brotli")] ContentDecoder::Br(ref mut decoder) => match decoder.flush() { Ok(()) => { let b = decoder.get_mut().take(); @@ -172,6 +187,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-gzip")] ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -185,6 +201,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-gzip")] ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -197,6 +214,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-zstd")] ContentDecoder::Zstd(ref mut decoder) => match decoder.flush() { Ok(_) => { let b = decoder.get_mut().take(); @@ -213,6 +231,7 @@ impl ContentDecoder { fn feed_data(&mut self, data: Bytes) -> io::Result> { match self { + #[cfg(feature = "compress-brotli")] ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -227,6 +246,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-gzip")] ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -241,6 +261,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-gzip")] ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -255,6 +276,7 @@ impl ContentDecoder { Err(e) => Err(e), }, + #[cfg(feature = "compress-zstd")] ContentDecoder::Zstd(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 36509b371..1e69990a0 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -9,12 +9,18 @@ use std::{ }; use actix_rt::task::{spawn_blocking, JoinHandle}; -use brotli2::write::BrotliEncoder; use bytes::Bytes; use derive_more::Display; -use flate2::write::{GzEncoder, ZlibEncoder}; use futures_core::ready; use pin_project::pin_project; + +#[cfg(feature = "compress-brotli")] +use brotli2::write::BrotliEncoder; + +#[cfg(feature = "compress-gzip")] +use flate2::write::{GzEncoder, ZlibEncoder}; + +#[cfg(feature = "compress-zstd")] use zstd::stream::write::Encoder as ZstdEncoder; use crate::{ @@ -233,28 +239,36 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { } enum ContentEncoder { + #[cfg(feature = "compress-gzip")] Deflate(ZlibEncoder), + #[cfg(feature = "compress-gzip")] Gzip(GzEncoder), + #[cfg(feature = "compress-brotli")] Br(BrotliEncoder), // We need explicit 'static lifetime here because ZstdEncoder need lifetime // argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static` + #[cfg(feature = "compress-zstd")] Zstd(ZstdEncoder<'static, Writer>), } impl ContentEncoder { fn encoder(encoding: ContentEncoding) -> Option { match encoding { + #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( Writer::new(), flate2::Compression::fast(), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( Writer::new(), flate2::Compression::fast(), ))), + #[cfg(feature = "compress-brotli")] ContentEncoding::Br => { Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) } + #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => { let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?; Some(ContentEncoder::Zstd(encoder)) @@ -266,27 +280,35 @@ impl ContentEncoder { #[inline] pub(crate) fn take(&mut self) -> Bytes { match *self { + #[cfg(feature = "compress-brotli")] ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(), } } fn finish(self) -> Result { match self { + #[cfg(feature = "compress-brotli")] ContentEncoder::Br(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), @@ -296,6 +318,7 @@ impl ContentEncoder { fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { + #[cfg(feature = "compress-brotli")] ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { @@ -303,6 +326,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { @@ -310,6 +334,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { @@ -317,6 +342,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 9f94faaa5..924d5441f 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,12 +1,14 @@ //! HTTP primitives for the Actix ecosystem. //! //! ## Crate Features -//! | Feature | Functionality | -//! | ---------------- | ----------------------------------------------------- | -//! | `openssl` | TLS support via [OpenSSL]. | -//! | `rustls` | TLS support via [rustls]. | -//! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | -//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | +//! | Feature | Functionality | +//! | ------------------- | ------------------------------------------- | +//! | `openssl` | TLS support via [OpenSSL]. | +//! | `rustls` | TLS support via [rustls]. | +//! | `compress-brotli` | Payload compression support: Brotli. | +//! | `compress-gzip` | Payload compression support: Deflate, Gzip. | +//! | `compress-zstd` | Payload compression support: Zstd. | +//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! //! [OpenSSL]: https://crates.io/crates/openssl //! [rustls]: https://crates.io/crates/rustls @@ -32,7 +34,8 @@ pub mod body; mod builder; pub mod client; mod config; -#[cfg(feature = "compress")] + +#[cfg(feature = "__compress")] pub mod encoding; mod extensions; pub mod header; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c66c6cda8..3ef7a804d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,11 +2,15 @@ ## Unreleased - 2021-xx-xx +### Changed + +* Change compression algorithm features flags. [#2250] + +[#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 * No significant changes since 3.0.0-beta.5. - ## 3.0.0-beta.5 - 2021-04-17 ### Removed * Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 645f70101..7d6ee52c4 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -24,10 +24,10 @@ path = "src/lib.rs" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress", "cookies"] +features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"] [features] -default = ["compress", "cookies"] +default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] # openssl openssl = ["tls-openssl", "actix-http/openssl"] @@ -35,8 +35,12 @@ openssl = ["tls-openssl", "actix-http/openssl"] # rustls rustls = ["tls-rustls", "actix-http/rustls"] -# content-encoding support -compress = ["actix-http/compress"] +# Brotli algorithm content-encoding support +compress-brotli = ["actix-http/compress-brotli", "__compress"] +# Gzip and deflate algorithms content-encoding support +compress-gzip = ["actix-http/compress-gzip", "__compress"] +# Zstd algorithm content-encoding support +compress-zstd = ["actix-http/compress-zstd", "__compress"] # cookie parsing and cookie jar cookies = ["cookie"] @@ -44,6 +48,10 @@ cookies = ["cookie"] # trust-dns as dns resolver trust-dns = ["actix-http/trust-dns"] +# Internal (PRIVATE!) features used to aid testing and cheking feature status. +# Don't rely on these whatsoever. They may disappear at anytime. +__compress = [] + [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" @@ -52,6 +60,7 @@ actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" +cfg-if = "1" cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } diff --git a/awc/src/request.rs b/awc/src/request.rs index c95cee839..46dae7fa3 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,7 +8,7 @@ use actix_http::{ body::Body, http::{ header::{self, IntoHeaderPair}, - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, + ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }, RequestHead, }; @@ -22,11 +22,6 @@ use crate::{ ClientConfig, }; -#[cfg(feature = "compress")] -const HTTPS_ENCODING: &str = "br, gzip, deflate"; -#[cfg(not(feature = "compress"))] -const HTTPS_ENCODING: &str = "br"; - /// An HTTP Client request builder /// /// This type can be used to construct an instance of `ClientRequest` through a @@ -480,22 +475,37 @@ impl ClientRequest { let mut slf = self; + // Set Accept-Encoding HTTP header depending on enabled feature. + // If decompress is not ask, then we are not able to find which encoding is + // supported, so we cannot guess Accept-Encoding HTTP header. if slf.response_decompress { - let https = slf - .head - .uri - .scheme() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true); + // Set Accept-Encoding with compression algorithm awc is built with. + #[cfg(feature = "__compress")] + let accept_encoding = { + let mut encoding = vec![]; - if https { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)); - } else { - #[cfg(feature = "compress")] + #[cfg(feature = "compress-brotli")] + encoding.push("br"); + + #[cfg(feature = "compress-gzip")] { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")); + encoding.push("gzip"); + encoding.push("deflate"); } + + #[cfg(feature = "compress-zstd")] + encoding.push("zstd"); + + assert!(!encoding.is_empty(), "encoding cannot be empty unless __compress feature has been explictily enabled."); + encoding.join(", ") }; + + // Otherwise tell the server, we do not support any compression algorithm. + // So we clearly indicate that we do want identity encoding. + #[cfg(not(feature = "__compress"))] + let accept_encoding = "identity"; + + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, accept_encoding)); } Ok(slf) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 7ac9c8ce9..c0639606e 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -22,7 +22,7 @@ use derive_more::From; use futures_core::Stream; use serde::Serialize; -#[cfg(feature = "compress")] +#[cfg(feature = "__compress")] use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use crate::{ @@ -91,7 +91,7 @@ impl SendClientRequest { } } -#[cfg(feature = "compress")] +#[cfg(feature = "__compress")] impl Future for SendClientRequest { type Output = Result>>, SendRequestError>; @@ -131,7 +131,7 @@ impl Future for SendClientRequest { } } -#[cfg(not(feature = "compress"))] +#[cfg(not(feature = "__compress"))] impl Future for SendClientRequest { type Output = Result; diff --git a/src/http/header/encoding.rs b/src/http/header/encoding.rs index aa49dea45..ce31c100f 100644 --- a/src/http/header/encoding.rs +++ b/src/http/header/encoding.rs @@ -1,7 +1,7 @@ use std::{fmt, str}; pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, + Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, Zstd, }; /// A value to represent an encoding used in `Transfer-Encoding` @@ -22,6 +22,8 @@ pub enum Encoding { Identity, /// The `trailers` encoding. Trailers, + /// The `zstd` encoding. + Zstd, /// Some other encoding that is less common, can be any String. EncodingExt(String), } @@ -36,6 +38,7 @@ impl fmt::Display for Encoding { Compress => "compress", Identity => "identity", Trailers => "trailers", + Zstd => "zstd", EncodingExt(ref s) => s.as_ref(), }) } @@ -52,6 +55,7 @@ impl str::FromStr for Encoding { "compress" => Ok(Compress), "identity" => Ok(Identity), "trailers" => Ok(Trailers), + "zstd" => Ok(Zstd), _ => Ok(EncodingExt(s.to_owned())), } } diff --git a/src/lib.rs b/src/lib.rs index b488b962b..4e04c9079 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ //! * Streaming and pipelining //! * Keep-alive and slow requests handling //! * Client/server [WebSockets](https://actix.rs/docs/websockets/) support -//! * Transparent content compression/decompression (br, gzip, deflate) +//! * Transparent content compression/decompression (br, gzip, deflate, zstd) //! * Powerful [request routing](https://actix.rs/docs/url-dispatch/) //! * Multipart streams //! * Static assets @@ -140,7 +140,8 @@ pub mod dev { pub use actix_http::body::{ AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream, }; - #[cfg(feature = "compress")] + + #[cfg(feature = "__compress")] pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 95f5f4b52..0a6256fe2 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -144,7 +144,7 @@ mod tests { use crate::{web, App, HttpResponse}; #[actix_rt::test] - #[cfg(all(feature = "cookies", feature = "compress"))] + #[cfg(all(feature = "cookies", feature = "__compress"))] async fn test_scope_middleware() { use crate::middleware::Compress; @@ -167,7 +167,7 @@ mod tests { } #[actix_rt::test] - #[cfg(all(feature = "cookies", feature = "compress"))] + #[cfg(all(feature = "cookies", feature = "__compress"))] async fn test_resource_scope_middleware() { use crate::middleware::Compress; diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index e24782f07..96a361fcf 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -14,7 +14,8 @@ pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::logger::Logger; pub use self::normalize::{NormalizePath, TrailingSlash}; -#[cfg(feature = "compress")] +#[cfg(feature = "__compress")] mod compress; -#[cfg(feature = "compress")] + +#[cfg(feature = "__compress")] pub use self::compress::Compress; diff --git a/src/types/form.rs b/src/types/form.rs index ce85983d1..44d1b952e 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -16,7 +16,7 @@ use futures_core::{future::LocalBoxFuture, ready}; use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; -#[cfg(feature = "compress")] +#[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error, @@ -255,9 +255,9 @@ impl Default for FormConfig { /// - content type is not `application/x-www-form-urlencoded` /// - content length is greater than [limit](UrlEncoded::limit()) pub struct UrlEncoded { - #[cfg(feature = "compress")] + #[cfg(feature = "__compress")] stream: Option>, - #[cfg(not(feature = "compress"))] + #[cfg(not(feature = "__compress"))] stream: Option, limit: usize, @@ -293,10 +293,15 @@ impl UrlEncoded { } }; - #[cfg(feature = "compress")] - let payload = Decompress::from_headers(payload.take(), req.headers()); - #[cfg(not(feature = "compress"))] - let payload = payload.take(); + let payload = { + cfg_if::cfg_if! { + if #[cfg(feature = "__compress")] { + Decompress::from_headers(payload.take(), req.headers()) + } else { + payload.take() + } + } + }; UrlEncoded { encoding, diff --git a/src/types/json.rs b/src/types/json.rs index 44b548355..fc02c8854 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -16,7 +16,7 @@ use serde::{de::DeserializeOwned, Serialize}; use actix_http::Payload; -#[cfg(feature = "compress")] +#[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ error::{Error, JsonPayloadError}, @@ -300,9 +300,9 @@ pub enum JsonBody { Body { limit: usize, length: Option, - #[cfg(feature = "compress")] + #[cfg(feature = "__compress")] payload: Decompress, - #[cfg(not(feature = "compress"))] + #[cfg(not(feature = "__compress"))] payload: Payload, buf: BytesMut, _res: PhantomData, @@ -345,10 +345,15 @@ where // As the internal usage always call JsonBody::limit after JsonBody::new. // And limit check to return an error variant of JsonBody happens there. - #[cfg(feature = "compress")] - let payload = Decompress::from_headers(payload.take(), req.headers()); - #[cfg(not(feature = "compress"))] - let payload = payload.take(); + let payload = { + cfg_if::cfg_if! { + if #[cfg(feature = "__compress")] { + Decompress::from_headers(payload.take(), req.headers()) + } else { + payload.take() + } + } + }; JsonBody::Body { limit: DEFAULT_LIMIT, diff --git a/src/types/payload.rs b/src/types/payload.rs index d69e0a126..3b0d1d6c6 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -282,9 +282,9 @@ impl Default for PayloadConfig { pub struct HttpMessageBody { limit: usize, length: Option, - #[cfg(feature = "compress")] + #[cfg(feature = "__compress")] stream: dev::Decompress, - #[cfg(not(feature = "compress"))] + #[cfg(not(feature = "__compress"))] stream: dev::Payload, buf: BytesMut, err: Option, @@ -312,10 +312,15 @@ impl HttpMessageBody { } } - #[cfg(feature = "compress")] - let stream = dev::Decompress::from_headers(payload.take(), req.headers()); - #[cfg(not(feature = "compress"))] - let stream = payload.take(); + let stream = { + cfg_if::cfg_if! { + if #[cfg(feature = "__compress")] { + dev::Decompress::from_headers(payload.take(), req.headers()) + } else { + payload.take() + } + } + }; HttpMessageBody { stream, From 73a655544efbc1a4550a98a62945f367fa6b783f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 19 Jun 2021 20:23:06 +0100 Subject: [PATCH 015/861] tweak compress feature docs --- CHANGES.md | 4 +--- README.md | 2 +- actix-http/CHANGES.md | 3 +-- awc/CHANGES.md | 4 ++-- awc/examples/client.rs | 12 +++++++----- awc/src/lib.rs | 44 ++++++++++++++++++++++++++++-------------- src/lib.rs | 4 +++- 7 files changed, 44 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 83a683eb6..17ae711d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,19 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx - ### Changed - * Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 + ## 4.0.0-beta.7 - 2021-06-17 ### Added * `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] ### Changed - * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] [#2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] diff --git a/README.md b/README.md index c6ef6d868..d9048a06b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ * Streaming and pipelining * Keep-alive and slow requests handling * Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) +* Transparent content compression/decompression (br, gzip, deflate, zstd) * Powerful [request routing](https://actix.rs/docs/url-dispatch/) * Multipart streams * Static assets diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 83f3a0527..c8d65e393 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx - ### Changed - * Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 + ## 3.0.0-beta.7 - 2021-06-17 ### Added * Alias `body::Body` as `body::AnyBody`. [#2215] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3ef7a804d..2e56eb958 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,16 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx - ### Changed - * Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 + ## 3.0.0-beta.6 - 2021-06-17 * No significant changes since 3.0.0-beta.5. + ## 3.0.0-beta.5 - 2021-04-17 ### Removed * Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] diff --git a/awc/examples/client.rs b/awc/examples/client.rs index 234ee3ae4..653cb226f 100644 --- a/awc/examples/client.rs +++ b/awc/examples/client.rs @@ -2,17 +2,19 @@ use std::error::Error as StdError; #[actix_web::main] async fn main() -> Result<(), Box> { - std::env::set_var("RUST_LOG", "actix_http=trace"); + std::env::set_var("RUST_LOG", "client=trace,awc=trace,actix_http=trace"); env_logger::init(); let client = awc::Client::new(); // Create request builder, configure request and send - let mut response = client + let request = client .get("https://www.rust-lang.org/") - .append_header(("User-Agent", "Actix-web")) - .send() - .await?; + .append_header(("User-Agent", "Actix-web")); + + println!("Request: {:?}", request); + + let mut response = request.send().await?; // server http response println!("Response: {:?}", response); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 122f3845c..c0290ddcf 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,7 +1,6 @@ //! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. //! -//! ## Making a GET request -//! +//! # Making a GET request //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -16,10 +15,8 @@ //! # } //! ``` //! -//! ## Making POST requests -//! -//! ### Raw body contents -//! +//! # Making POST requests +//! ## Raw body contents //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -31,8 +28,7 @@ //! # } //! ``` //! -//! ### Forms -//! +//! ## Forms //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -46,8 +42,7 @@ //! # } //! ``` //! -//! ### JSON -//! +//! ## JSON //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -64,8 +59,24 @@ //! # } //! ``` //! -//! ## WebSocket support +//! # Response Compression +//! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! +//! The `Accept-Encoding` header will automatically be populated with enabled codecs and added to +//! outgoing requests, allowing servers to select their `Content-Encoding` accordingly. +//! +//! Feature flags enable these codecs according to the table below. By default, all `compress-*` +//! features are enabled. +//! +//! | Feature | Codecs | +//! | ----------------- | ------------- | +//! | `compress-brotli` | brotli | +//! | `compress-gzip` | gzip, deflate | +//! | `compress-zstd` | zstd | +//! +//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding +//! +//! # WebSocket support //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { @@ -128,6 +139,9 @@ pub use self::sender::SendClientRequest; /// An asynchronous HTTP and WebSocket client. /// +/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU +/// and memory usage. +/// /// # Examples /// ``` /// use awc::Client; @@ -136,10 +150,10 @@ pub use self::sender::SendClientRequest; /// async fn main() { /// let mut client = Client::default(); /// -/// let res = client.get("http://www.rust-lang.org") // <- Create request builder -/// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send HTTP request -/// .await; // <- send request and wait for response +/// let res = client.get("http://www.rust-lang.org") +/// .insert_header(("User-Agent", "my-app/1.2")) +/// .send() +/// .await; /// /// println!("Response: {:?}", res); /// } diff --git a/src/lib.rs b/src/lib.rs index 4e04c9079..4bcef3988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,8 +57,10 @@ //! * Runs on stable Rust 1.46+ //! //! # Crate Features -//! * `compress` - content encoding compression support (enabled by default) //! * `cookies` - cookies support (enabled by default) +//! * `compress-brotli` - brotli content encoding compression support (enabled by default) +//! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default) +//! * `compress-zstd` - zstd content encoding compression support (enabled by default) //! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `secure-cookies` - secure cookies support From 689377328008d8b9f3a9b68347fdfbe2a31a6542 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sat, 19 Jun 2021 23:00:31 +0300 Subject: [PATCH 016/861] files: allow `show_files_listing()` with `index_file()` (#2228) --- actix-files/CHANGES.md | 2 ++ actix-files/src/files.rs | 8 ++++++- actix-files/src/lib.rs | 29 ++++++++++++++++++++++++ actix-files/src/service.rs | 46 ++++++++++++++++++++------------------ 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 9a643f127..bec67dd4e 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -8,11 +8,13 @@ * For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] * `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] * `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 [#2225]: https://github.com/actix/actix-web/pull/2225 [#2257]: https://github.com/actix/actix-web/pull/2257 +[#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.4 - 2021-04-02 diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index fc8fd2531..c48cf59a7 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -114,6 +114,9 @@ impl Files { /// Show files listing for directories. /// /// By default show files listing is disabled. + /// + /// When used with [`Files::index_file()`], files listing is shown as a fallback + /// when the index file is not found. pub fn show_files_listing(mut self) -> Self { self.show_index = true; self @@ -148,8 +151,11 @@ impl Files { /// Set index file /// - /// Shows specific index file for directory "/" instead of + /// Shows specific index file for directories instead of /// showing files listing. + /// + /// If the index file is not found, files listing is shown as a fallback if + /// [`Files::show_files_listing()`] is set. pub fn index_file>(mut self, index: T) -> Self { self.index = Some(index.into()); self diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 48d3c49f4..c9cc79193 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -872,4 +872,33 @@ mod tests { "inline; filename=\"symlink-test.png\"" ); } + + #[actix_rt::test] + async fn test_index_with_show_files_listing() { + let service = Files::new(".", ".") + .index_file("lib.rs") + .show_files_listing() + .new_service(()) + .await + .unwrap(); + + // Serve the index if exists + let req = TestRequest::default().uri("/src").to_srv_request(); + let resp = test::call_service(&service, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-rust" + ); + + // Show files listing, otherwise. + let req = TestRequest::default().uri("/tests").to_srv_request(); + let resp = test::call_service(&service, req).await; + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/html; charset=utf-8" + ); + let bytes = test::read_body(resp).await; + assert!(format!("{:?}", bytes).contains("/tests/test.png")); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 831115dc6..64938e5ef 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -102,26 +102,20 @@ impl Service for FilesService { ))); } - if let Some(ref redir_index) = self.index { - let path = path.join(redir_index); - - match NamedFile::open(path) { - Ok(mut named_file) => { - if let Some(ref mime_override) = self.mime_override { - let new_disposition = - mime_override(&named_file.content_type.type_()); - named_file.content_disposition.disposition = new_disposition; - } - named_file.flags = self.file_flags; - - let (req, _) = req.into_parts(); - let res = named_file.into_response(&req); - Box::pin(ok(ServiceResponse::new(req, res))) - } - Err(err) => self.handle_err(err, req), + let serve_named_file = |req: ServiceRequest, mut named_file: NamedFile| { + if let Some(ref mime_override) = self.mime_override { + let new_disposition = mime_override(&named_file.content_type.type_()); + named_file.content_disposition.disposition = new_disposition; } - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); + named_file.flags = self.file_flags; + + let (req, _) = req.into_parts(); + let res = named_file.into_response(&req); + Box::pin(ok(ServiceResponse::new(req, res))) + }; + + let show_index = |req: ServiceRequest| { + let dir = Directory::new(self.directory.clone(), path.clone()); let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); @@ -130,11 +124,19 @@ impl Service for FilesService { Ok(resp) => ok(resp), Err(err) => ok(ServiceResponse::from_err(err, req)), }) - } else { - Box::pin(ok(ServiceResponse::from_err( + }; + + match self.index { + Some(ref index) => match NamedFile::open(path.join(index)) { + Ok(named_file) => serve_named_file(req, named_file), + Err(_) if self.show_index => show_index(req), + Err(err) => self.handle_err(err, req), + }, + None if self.show_index => show_index(req), + _ => Box::pin(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, - ))) + ))), } } else { match NamedFile::open(path) { From f81d4bdae755fe1a7ba60dcde1ceae39e50771fb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 19 Jun 2021 23:40:30 +0100 Subject: [PATCH 017/861] remove unused private hidden methods --- src/request.rs | 12 ------------ src/service.rs | 6 ------ 2 files changed, 18 deletions(-) diff --git a/src/request.rs b/src/request.rs index 42c722c46..a364f8b1f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -60,18 +60,6 @@ impl HttpRequest { }), } } - - #[doc(hidden)] - pub fn __priv_test_new( - path: Path, - head: Message, - rmap: Rc, - config: AppConfig, - app_data: Rc, - ) -> HttpRequest { - let app_state = AppInitServiceState::new(rmap, config); - Self::new(path, head, app_state, app_data) - } } impl HttpRequest { diff --git a/src/service.rs b/src/service.rs index 2956fe6cb..e3e975cef 100644 --- a/src/service.rs +++ b/src/service.rs @@ -74,12 +74,6 @@ impl ServiceRequest { Self { req, payload } } - /// Construct service request. - #[doc(hidden)] - pub fn __priv_test_new(req: HttpRequest, payload: Payload) -> Self { - Self::new(req, payload) - } - /// Deconstruct request into parts #[inline] pub fn into_parts(self) -> (HttpRequest, Payload) { From 7faeffc5ab240bb6101724adf315d77c530e72b4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 20 Jun 2021 19:47:42 +0100 Subject: [PATCH 018/861] prepare actix-test release 0.1.0-beta.3 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 4 ++++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- src/config.rs | 1 + 8 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 770c9a050..320751c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.6", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 44c29dc92..65dce628b 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -36,4 +36,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.7" -actix-test = "0.1.0-beta.2" +actix-test = "0.1.0-beta.3" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 2276fe745..fa554ba2e 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.3 - 2021-06-20 +* No significant changes from `0.1.0-beta.2`. + + ## 0.1.0-beta.2 - 2021-04-17 * No significant changes from `0.1.0-beta.1`. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 607038377..ca814e0e5 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 159b10d58..deb461760 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,7 +29,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.2" +actix-test = "0.1.0-beta.3" awc = { version = "3.0.0-beta.6", default-features = false } env_logger = "0.8" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 29565f74a..327f16bc5 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.2" +actix-test = "0.1.0-beta.3" actix-utils = "3.0.0" actix-web = "4.0.0-beta.7" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7d6ee52c4..26c625a05 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -83,7 +83,7 @@ actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } -actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" diff --git a/src/config.rs b/src/config.rs index 4bd76f2b7..d22bc856e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -116,6 +116,7 @@ impl AppConfig { AppConfig { secure, host, addr } } + /// Needed in actix-test crate. #[doc(hidden)] pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { AppConfig::new(secure, host, addr) From 2d8530feb37447a1dd2e58700b31b987ae8163ef Mon Sep 17 00:00:00 2001 From: Grzegorz Baranski Date: Tue, 22 Jun 2021 15:00:28 +0200 Subject: [PATCH 019/861] chore: bump actix to 0.12.0 in actix-web-actors (#2277) Co-authored-by: Rob Ede --- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index a7ee7a9e1..decbe2219 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Update `actix` to `0.12`. [#2277] + +[#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index deb461760..669cd4001 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 = { version = "0.11.0-beta.3", default-features = false } +actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.0" actix-http = "3.0.0-beta.7" actix-web = { version = "4.0.0-beta.7", default-features = false } From 12f7720309a5bffc0ae93ece4b7a0462ed901f56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Jun 2021 15:50:58 +0100 Subject: [PATCH 020/861] deprecate `App::data` and `App::data_factory` (#2271) --- CHANGES.md | 2 ++ src/app.rs | 15 ++++++++++++--- src/app_service.rs | 2 ++ src/data.rs | 6 ++++++ src/request.rs | 2 ++ src/resource.rs | 5 +++++ src/scope.rs | 5 +++++ src/service.rs | 2 ++ src/test.rs | 2 ++ src/types/payload.rs | 2 ++ tests/test_server.rs | 2 ++ 11 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 17ae711d6..fa4aa8595 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,8 +3,10 @@ ## Unreleased - 2021-xx-xx ### Changed * Change compression algorithm features flags. [#2250] +* Deprecate `App::data` and `App::data_factory`. [#2271] [#2250]: https://github.com/actix/actix-web/pull/2250 +[#2271]: https://github.com/actix/actix-web/pull/2271 ## 4.0.0-beta.7 - 2021-06-17 diff --git a/src/app.rs b/src/app.rs index 357d45eeb..8c622dd36 100644 --- a/src/app.rs +++ b/src/app.rs @@ -98,13 +98,18 @@ where /// web::resource("/index.html").route( /// web::get().to(index))); /// ``` + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) } - /// Set application data factory. This function is - /// similar to `.data()` but it accepts data factory. Data object get - /// constructed asynchronously during application initialization. + /// Add application data factory. This function is similar to `.data()` but it accepts a + /// "data factory". Data values are constructed asynchronously during application + /// initialization, before the server starts accepting requests. + #[deprecated( + since = "4.0.0", + note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead." + )] pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, @@ -518,6 +523,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_data_factory() { let srv = init_service( @@ -541,6 +548,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_data_factory_errors() { let srv = try_init_service( diff --git a/src/app_service.rs b/src/app_service.rs index ca6f36202..a9247b19f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -349,6 +349,8 @@ mod tests { } } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_drop_data() { let data = Arc::new(AtomicBool::new(false)); diff --git a/src/data.rs b/src/data.rs index f09a88891..51db6ce4c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -154,6 +154,8 @@ mod tests { web, App, HttpResponse, }; + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_data_extractor() { let srv = init_service(App::new().data("TEST".to_string()).service( @@ -221,6 +223,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_route_data_extractor() { let srv = init_service( @@ -250,6 +254,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_override_data() { let srv = diff --git a/src/request.rs b/src/request.rs index a364f8b1f..bff66f08e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -711,6 +711,8 @@ mod tests { assert_eq!(body, Bytes::from_static(b"1")); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_extensions_dropped() { struct Tracker { diff --git a/src/resource.rs b/src/resource.rs index 8c2b83b60..9455895e9 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -196,6 +196,7 @@ where /// )); /// } /// ``` + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) } @@ -694,6 +695,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::NO_CONTENT); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_data() { let srv = init_service( @@ -726,6 +729,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_data_default_service() { let srv = init_service( diff --git a/src/scope.rs b/src/scope.rs index 412c01d95..86304074b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -146,6 +146,7 @@ where /// ); /// } /// ``` + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) } @@ -990,6 +991,8 @@ mod tests { ); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_override_data() { let srv = init_service(App::new().data(1usize).service( @@ -1008,6 +1011,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_override_data_default_service() { let srv = init_service(App::new().data(1usize).service( diff --git a/src/service.rs b/src/service.rs index e3e975cef..a772e20b7 100644 --- a/src/service.rs +++ b/src/service.rs @@ -649,6 +649,8 @@ mod tests { assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_service_data() { let srv = diff --git a/src/test.rs b/src/test.rs index de97dc8aa..05a4ba7f2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -839,6 +839,8 @@ mod tests { assert!(res.status().is_success()); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_server_data() { async fn handler(data: web::Data) -> impl Responder { diff --git a/src/types/payload.rs b/src/types/payload.rs index 3b0d1d6c6..87378701b 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -398,6 +398,8 @@ mod tests { assert!(cfg.check_mimetype(&req).is_ok()); } + // allow deprecated App::data + #[allow(deprecated)] #[actix_rt::test] async fn test_config_recall_locations() { async fn bytes_handler(_: Bytes) -> impl Responder { diff --git a/tests/test_server.rs b/tests/test_server.rs index 520eb5ce2..9131d1f29 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1028,6 +1028,8 @@ async fn test_normalize() { assert!(response.status().is_success()); } +// allow deprecated App::data +#[allow(deprecated)] #[actix_rt::test] async fn test_data_drop() { use std::sync::{ From b1148fd735fddd03d3717b2a90b697be51fac4d2 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 22 Jun 2021 12:32:03 -0400 Subject: [PATCH 021/861] Implement `FromRequest` for request parts (#2263) Co-authored-by: Rob Ede --- CHANGES.md | 5 +++ src/extract.rs | 68 ++++++++++++++++++++++++++++-- src/info.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- 4 files changed, 177 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa4aa8595..b69b5a18c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Add extractors for `Uri` and `Method`. [#2263] +* Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263] + ### Changed * Change compression algorithm features flags. [#2250] * Deprecate `App::data` and `App::data_factory`. [#2271] [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 +[#2263]: https://github.com/actix/actix-web/pull/2263 ## 4.0.0-beta.7 - 2021-06-17 diff --git a/src/extract.rs b/src/extract.rs index 45cb330a3..d7b67cd90 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,12 +1,14 @@ //! Request extractors use std::{ + convert::Infallible, future::Future, pin::Pin, task::{Context, Poll}, }; -use actix_utils::future::{ready, Ready}; +use actix_http::http::{Method, Uri}; +use actix_utils::future::{ok, Ready}; use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; @@ -216,14 +218,58 @@ where } } +/// Extract the request's URI. +/// +/// # Examples +/// ``` +/// use actix_web::{http::Uri, web, App, Responder}; +/// +/// async fn handler(uri: Uri) -> impl Responder { +/// format!("Requested path: {}", uri.path()) +/// } +/// +/// let app = App::new().default_service(web::to(handler)); +/// ``` +impl FromRequest for Uri { + type Error = Infallible; + type Future = Ready>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ok(req.uri().clone()) + } +} + +/// Extract the request's method. +/// +/// # Examples +/// ``` +/// use actix_web::{http::Method, web, App, Responder}; +/// +/// async fn handler(method: Method) -> impl Responder { +/// format!("Request method: {}", method) +/// } +/// +/// let app = App::new().default_service(web::to(handler)); +/// ``` +impl FromRequest for Method { + type Error = Infallible; + type Future = Ready>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ok(req.method().clone()) + } +} + #[doc(hidden)] impl FromRequest for () { - type Error = Error; - type Future = Ready>; + type Error = Infallible; + type Future = Ready>; type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - ready(Ok(())) + ok(()) } } @@ -411,4 +457,18 @@ mod tests { .unwrap(); assert!(r.is_err()); } + + #[actix_rt::test] + async fn test_uri() { + let req = TestRequest::default().uri("/foo/bar").to_http_request(); + let uri = Uri::extract(&req).await.unwrap(); + assert_eq!(uri.path(), "/foo/bar"); + } + + #[actix_rt::test] + async fn test_method() { + let req = TestRequest::default().method(Method::GET).to_http_request(); + let method = Method::extract(&req).await.unwrap(); + assert_eq!(method, Method::GET); + } } diff --git a/src/info.rs b/src/info.rs index c9ddf6ec4..c6ff54efe 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,13 +1,36 @@ -use std::cell::Ref; +use std::{cell::Ref, convert::Infallible, net::SocketAddr}; -use crate::dev::{AppConfig, RequestHead}; -use crate::http::header::{self, HeaderName}; +use actix_utils::future::{err, ok, Ready}; +use derive_more::{Display, Error}; + +use crate::{ + dev::{AppConfig, Payload, RequestHead}, + http::header::{self, HeaderName}, + FromRequest, HttpRequest, ResponseError, +}; const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; -/// `HttpRequest` connection information +/// HTTP connection information. +/// +/// `ConnectionInfo` implements `FromRequest` and can be extracted in handlers. +/// +/// # Examples +/// ``` +/// # use actix_web::{HttpResponse, Responder}; +/// use actix_web::dev::ConnectionInfo; +/// +/// async fn handler(conn: ConnectionInfo) -> impl Responder { +/// match conn.host() { +/// "actix.rs" => HttpResponse::Ok().body("Welcome!"), +/// "admin.actix.rs" => HttpResponse::Ok().body("Admin portal."), +/// _ => HttpResponse::NotFound().finish() +/// } +/// } +/// # let _svc = actix_web::web::to(handler); +/// ``` #[derive(Debug, Clone, Default)] pub struct ConnectionInfo { scheme: String, @@ -187,6 +210,65 @@ impl ConnectionInfo { } } +impl FromRequest for ConnectionInfo { + type Error = Infallible; + type Future = Ready>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ok(req.connection_info().clone()) + } +} + +/// Extractor for peer's socket address. +/// +/// Also see [`HttpRequest::peer_addr`]. +/// +/// # Examples +/// ``` +/// # use actix_web::Responder; +/// use actix_web::dev::PeerAddr; +/// +/// async fn handler(peer_addr: PeerAddr) -> impl Responder { +/// let socket_addr = peer_addr.0; +/// socket_addr.to_string() +/// } +/// # let _svc = actix_web::web::to(handler); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)] +#[display(fmt = "{}", _0)] +pub struct PeerAddr(pub SocketAddr); + +impl PeerAddr { + /// Unwrap into inner `SocketAddr` value. + pub fn into_inner(self) -> SocketAddr { + self.0 + } +} + +#[derive(Debug, Display, Error)] +#[non_exhaustive] +#[display(fmt = "Missing peer address")] +pub struct MissingPeerAddr; + +impl ResponseError for MissingPeerAddr {} + +impl FromRequest for PeerAddr { + type Error = MissingPeerAddr; + type Future = Ready>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + match req.peer_addr() { + Some(addr) => ok(PeerAddr(addr)), + None => { + log::error!("Missing peer address."); + err(MissingPeerAddr) + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -239,4 +321,25 @@ mod tests { let info = req.connection_info(); assert_eq!(info.scheme(), "https"); } + + #[actix_rt::test] + async fn test_conn_info() { + let req = TestRequest::default() + .uri("http://actix.rs/") + .to_http_request(); + let conn_info = ConnectionInfo::extract(&req).await.unwrap(); + assert_eq!(conn_info.scheme(), "http"); + } + + #[actix_rt::test] + async fn test_peer_addr() { + let addr = "127.0.0.1:8080".parse().unwrap(); + let req = TestRequest::default().peer_addr(addr).to_http_request(); + let peer_addr = PeerAddr::extract(&req).await.unwrap(); + assert_eq!(peer_addr, PeerAddr(addr)); + + let req = TestRequest::default().to_http_request(); + let res = PeerAddr::extract(&req).await; + assert!(res.is_err()); + } } diff --git a/src/lib.rs b/src/lib.rs index 4bcef3988..9d8bf62e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ pub mod dev { pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] pub use crate::handler::Handler; - pub use crate::info::ConnectionInfo; + pub use crate::info::{ConnectionInfo, PeerAddr}; pub use crate::rmap::ResourceMap; pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; From 3b6333e65f2da0c0ddf84f014088ef69c12dd2e2 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Tue, 22 Jun 2021 23:22:33 +0200 Subject: [PATCH 022/861] Propagate error cause to middlewares (#2280) --- src/response/response.rs | 5 +- tests/test_error_propagation.rs | 100 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/test_error_propagation.rs diff --git a/src/response/response.rs b/src/response/response.rs index 9dd804be0..9a3bb2874 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -49,7 +49,10 @@ impl HttpResponse { /// Create an error response. #[inline] pub fn from_error(error: impl Into) -> Self { - error.into().as_response_error().error_response() + let error = error.into(); + let mut response = error.as_response_error().error_response(); + response.error = Some(error); + response } } diff --git a/tests/test_error_propagation.rs b/tests/test_error_propagation.rs new file mode 100644 index 000000000..1b56615a0 --- /dev/null +++ b/tests/test_error_propagation.rs @@ -0,0 +1,100 @@ +use actix_utils::future::{ok, Ready}; +use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; +use actix_web::test::{call_service, init_service, TestRequest}; +use actix_web::{HttpResponse, ResponseError}; +use futures_util::lock::Mutex; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +#[derive(Debug, Clone)] +pub struct MyError; + +impl ResponseError for MyError {} + +impl std::fmt::Display for MyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "A custom error") + } +} + +#[actix_web::get("/test")] +async fn test() -> Result { + Err(MyError)?; + Ok(HttpResponse::NoContent().finish()) +} + +#[derive(Clone)] +pub struct SpyMiddleware(Arc>>); + +impl Transform for SpyMiddleware +where + S: Service, Error = actix_web::Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = actix_web::Error; + type Transform = Middleware; + type InitError = (); + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(Middleware { + was_error: self.0.clone(), + service, + }) + } +} + +#[doc(hidden)] +pub struct Middleware { + was_error: Arc>>, + service: S, +} + +impl Service for Middleware +where + S: Service, Error = actix_web::Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = actix_web::Error; + type Future = Pin>>>; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&self, req: ServiceRequest) -> Self::Future { + let lock = self.was_error.clone(); + let response_future = self.service.call(req); + Box::pin(async move { + let response = response_future.await; + if let Ok(success) = &response { + *lock.lock().await = Some(success.response().error().is_some()); + } + response + }) + } +} + +#[actix_rt::test] +async fn error_cause_should_be_propagated_to_middlewares() { + let lock = Arc::new(Mutex::new(None)); + let spy_middleware = SpyMiddleware(lock.clone()); + + let app = init_service( + actix_web::App::new() + .wrap(spy_middleware.clone()) + .service(test), + ) + .await; + + call_service(&app, TestRequest::with_uri("/test").to_request()).await; + + let was_error_captured = lock.lock().await.unwrap(); + assert!(was_error_captured); +} From 8846808804e2ac885a5a81b27817b365dfcd4c9a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Tue, 22 Jun 2021 19:42:00 -0400 Subject: [PATCH 023/861] ServiceRequest::parts_mut (#2177) --- CHANGES.md | 2 ++ src/service.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b69b5a18c..31fa4690f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx ### Added +* Add `ServiceRequest::parts_mut`. [#2177] * Add extractors for `Uri` and `Method`. [#2263] * Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263] @@ -9,6 +10,7 @@ * Change compression algorithm features flags. [#2250] * Deprecate `App::data` and `App::data_factory`. [#2271] +[#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 [#2263]: https://github.com/actix/actix-web/pull/2263 diff --git a/src/service.rs b/src/service.rs index a772e20b7..8d73a87fa 100644 --- a/src/service.rs +++ b/src/service.rs @@ -80,6 +80,12 @@ impl ServiceRequest { (self.req, self.payload) } + /// Get mutable access to inner `HttpRequest` and `Payload` + #[inline] + pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) { + (&mut self.req, &mut self.payload) + } + /// Construct request from parts. pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { Self { req, payload } From 7535a1ade8c1ee392ae1aa337821cf8f7c7c4c9a Mon Sep 17 00:00:00 2001 From: Jonas Malaco Date: Wed, 23 Jun 2021 12:54:25 -0300 Subject: [PATCH 024/861] Note that Form cannot require data ordering (#2283) --- src/types/form.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/form.rs b/src/types/form.rs index 44d1b952e..4ce075d99 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -80,6 +80,10 @@ use crate::{ /// }) /// } /// ``` +/// +/// # Panics +/// URL encoded forms consist of unordered `key=value` pairs, therefore they cannot be decoded into +/// any type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Form(pub T); From ed0516d724ed225b0b83c01f9278e47bd5f7f104 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Jun 2021 20:47:17 +0100 Subject: [PATCH 025/861] try to fix doc test failures (#2284) --- .cargo/config.toml | 7 ++++--- .github/workflows/ci.yml | 36 ++++++++++++-------------------- .github/workflows/clippy-fmt.yml | 2 +- tests/test_server.rs | 2 +- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0cf09f710..72f445d8a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,6 +3,7 @@ chk = "check --workspace --all-features --tests --examples --bins" lint = "clippy --workspace --tests --examples" ci-min = "hack check --workspace --no-default-features" ci-min-test = "hack check --workspace --no-default-features --tests --examples" -ci-default = "hack check --workspace" -ci-full = "check --workspace --bins --examples --tests" -ci-test = "test --workspace --all-features --no-fail-fast" +ci-default = "check --workspace --bins --tests --examples" +ci-full = "check --workspace --all-features --bins --tests --examples" +ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" +ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c57db463a..be595e35c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,43 +66,33 @@ jobs: - name: check minimal uses: actions-rs/cargo@v1 - with: - command: hack - args: check --workspace --no-default-features + with: { command: ci-min } - name: check minimal + tests uses: actions-rs/cargo@v1 - with: - command: hack - args: check --workspace --no-default-features --tests --examples + with: { command: ci-min-test } + - name: check default + uses: actions-rs/cargo@v1 + with: { command: ci-default } + - name: check full uses: actions-rs/cargo@v1 - with: - command: check - args: --workspace --bins --examples --tests + with: { command: ci-full } - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - - - name: tests (actix-http) uses: actions-rs/cargo@v1 timeout-minutes: 40 with: - command: test - args: --package=actix-http --no-default-features --features=rustls -- --nocapture + command: ci-test + args: --skip=test_reading_deflate_encoding_large_random_rustls - - name: tests (awc) + - name: doc tests + # due to unknown issue with running doc tests on macOS + if: matrix.target.os == 'ubuntu-latest' uses: actions-rs/cargo@v1 timeout-minutes: 40 - with: - command: test - args: --package=awc --no-default-features --features=rustls -- --nocapture + with: { command: ci-doctest } - name: Generate coverage file if: > diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index e966fa4ab..957256d32 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -36,4 +36,4 @@ jobs: uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --workspace --tests --all-features + args: --workspace --all-features --tests diff --git a/tests/test_server.rs b/tests/test_server.rs index 9131d1f29..afea39dd9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -879,7 +879,7 @@ async fn test_brotli_encoding_large_openssl() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(all(feature = "rustls", feature = "openssl"))] +#[cfg(feature = "rustls")] mod plus_rustls { use std::io::BufReader; From 083ee05d50519897db96fb1873b6eebd5f6df186 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Wed, 23 Jun 2021 16:30:06 -0400 Subject: [PATCH 026/861] `Route::service` (#2262) Co-authored-by: Rob Ede --- CHANGES.md | 4 +- Cargo.toml | 1 - src/extract.rs | 8 ++-- src/lib.rs | 4 +- src/request.rs | 2 +- src/route.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 128 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 31fa4690f..876e1c03d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,8 @@ ### Added * Add `ServiceRequest::parts_mut`. [#2177] * Add extractors for `Uri` and `Method`. [#2263] -* Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add `Route::service` for using hand-written services as handlers. [#2262] ### Changed * Change compression algorithm features flags. [#2250] @@ -13,6 +14,7 @@ [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 +[#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 diff --git a/Cargo.toml b/Cargo.toml index 320751c66..779b52255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,6 @@ flate2 = "1.0.13" zstd = "0.7" rand = "0.8" rcgen = "0.8" -serde_derive = "1.0" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.19.0" } diff --git a/src/extract.rs b/src/extract.rs index d7b67cd90..592f7ab83 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -54,7 +54,7 @@ pub trait FromRequest: Sized { /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -145,7 +145,7 @@ where /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -265,7 +265,7 @@ impl FromRequest for Method { #[doc(hidden)] impl FromRequest for () { type Error = Infallible; - type Future = Ready>; + type Future = Ready>; type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { @@ -376,7 +376,7 @@ mod m { mod tests { use actix_http::http::header; use bytes::Bytes; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; use crate::test::TestRequest; diff --git a/src/lib.rs b/src/lib.rs index 9d8bf62e7..920abccb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,9 @@ pub mod dev { pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; - pub use actix_service::{always_ready, forward_ready, Service, Transform}; + pub use actix_service::{ + always_ready, fn_factory, fn_service, forward_ready, Service, Transform, + }; pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { diff --git a/src/request.rs b/src/request.rs index bff66f08e..5c5c43d26 100644 --- a/src/request.rs +++ b/src/request.rs @@ -347,7 +347,7 @@ impl Drop for HttpRequest { /// # Examples /// ``` /// use actix_web::{web, App, HttpRequest}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// /// /// extract `Thing` from request /// async fn index(req: HttpRequest) -> String { diff --git a/src/route.rs b/src/route.rs index 44f7e30b8..d85b940bd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use std::{future::Future, rc::Rc}; use actix_http::http::Method; use actix_service::{ boxed::{self, BoxService, BoxServiceFactory}, - Service, ServiceFactory, + Service, ServiceFactory, ServiceFactoryExt, }; use futures_core::future::LocalBoxFuture; @@ -128,9 +128,10 @@ impl Route { /// Set handler function, use request extractors for parameters. /// + /// # Examples /// ``` /// use actix_web::{web, http, App}; - /// use serde_derive::Deserialize; + /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -154,7 +155,7 @@ impl Route { /// /// ``` /// # use std::collections::HashMap; - /// # use serde_derive::Deserialize; + /// # use serde::Deserialize; /// use actix_web::{web, App}; /// /// #[derive(Deserialize)] @@ -184,6 +185,53 @@ impl Route { self.service = boxed::factory(HandlerService::new(handler)); self } + + /// Set raw service to be constructed and called as the request handler. + /// + /// # Examples + /// ``` + /// # use std::convert::Infallible; + /// # use futures_util::future::LocalBoxFuture; + /// # use actix_web::{*, dev::*, http::header}; + /// struct HelloWorld; + /// + /// impl Service for HelloWorld { + /// type Response = ServiceResponse; + /// type Error = Infallible; + /// type Future = LocalBoxFuture<'static, Result>; + /// + /// always_ready!(); + /// + /// fn call(&self, req: ServiceRequest) -> Self::Future { + /// let (req, _) = req.into_parts(); + /// + /// let res = HttpResponse::Ok() + /// .insert_header(header::ContentType::plaintext()) + /// .body("Hello world!"); + /// + /// Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + /// } + /// } + /// + /// App::new().route( + /// "/", + /// web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + /// ); + /// ``` + pub fn service(mut self, service_factory: S) -> Self + where + S: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = E, + InitError = (), + Config = (), + > + 'static, + E: Into + 'static, + { + self.service = boxed::factory(service_factory.map_err(Into::into)); + self + } } #[cfg(test)] @@ -192,9 +240,12 @@ mod tests { use actix_rt::time::sleep; use bytes::Bytes; - use serde_derive::Serialize; + use futures_core::future::LocalBoxFuture; + use serde::Serialize; - use crate::http::{Method, StatusCode}; + use crate::dev::{always_ready, fn_factory, fn_service, Service}; + use crate::http::{header, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, HttpResponse}; @@ -268,4 +319,65 @@ mod tests { let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } + + #[actix_rt::test] + async fn test_service_handler() { + struct HelloWorld; + + impl Service for HelloWorld { + type Response = ServiceResponse; + type Error = crate::Error; + type Future = LocalBoxFuture<'static, Result>; + + always_ready!(); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Hello world!"); + + Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + } + } + + let srv = init_service( + App::new() + .route( + "/hello", + web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + ) + .route( + "/bye", + web::get().service(fn_factory(|| async { + Ok::<_, ()>(fn_service(|req: ServiceRequest| async { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Goodbye, and thanks for all the fish!"); + + Ok::<_, Infallible>(ServiceResponse::new(req, res)) + })) + })), + ), + ) + .await; + + let req = TestRequest::get().uri("/hello").to_request(); + 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"Hello world!")); + + let req = TestRequest::get().uri("/bye").to_request(); + 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"Goodbye, and thanks for all the fish!") + ); + } } From 2d8d2f5ab08c7db6ccfeba5a15a5085165a06ed3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:10:51 +0100 Subject: [PATCH 027/861] app data doc improvements --- src/app.rs | 85 +++++++++++++++++++----------- src/app_service.rs | 9 +++- src/config.rs | 11 ++-- src/data.rs | 5 ++ src/resource.rs | 76 ++++++++++++++------------- src/scope.rs | 128 +++++++++++++++++++++++---------------------- src/service.rs | 3 +- src/web.rs | 1 - 8 files changed, 181 insertions(+), 137 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8c622dd36..677f73805 100644 --- a/src/app.rs +++ b/src/app.rs @@ -68,36 +68,71 @@ where InitError = (), >, { - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. + /// Set application (root level) data. /// - /// **Note**: HTTP server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application data must be constructed - /// multiple times. If you want to share data between different - /// threads, a shared object should be used, e.g. `Arc`. Internally `Data` type - /// uses `Arc` so data could be created outside of app factory and clones could - /// be stored via `App::app_data()` method. + /// Application data stored with `App::app_data()` method is available through the + /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. + /// + /// # [`Data`] + /// Any [`Data`] type added here can utilize it's extractor implementation in handlers. + /// Types not wrapped in `Data` cannot use this extractor. See [its docs](Data) for more + /// about its usage and patterns. /// /// ``` /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; /// /// struct MyData { - /// counter: Cell, + /// count: std::cell::Cell, /// } /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) /// } /// - /// let app = App::new() - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))); + /// let app = App::new().service( + /// web::resource("/") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route(web::get().to(handler)) + /// ); /// ``` + /// + /// # Shared Mutable State + /// [`HttpServer::new`](crate::HttpServer::new) accepts an application factory rather than an + /// application instance; the factory closure is called on each worker thread independently. + /// Therefore, if you want to share a data object between different workers, a shareable object + /// needs to be created first, outside the `HttpServer::new` closure and cloned into it. + /// [`Data`] is an example of such a sharable object. + /// + /// ```ignore + /// let counter = web::Data::new(AppStateWithCounter { + /// counter: Mutex::new(0), + /// }); + /// + /// HttpServer::new(move || { + /// // move counter object into the closure and clone for each worker + /// + /// App::new() + /// .app_data(counter.clone()) + /// .route("/", web::get().to(handler)) + /// }) + /// ``` + pub fn app_data(mut self, ext: U) -> Self { + self.extensions.insert(ext); + self + } + + /// Add application (root) data after wrapping in `Data`. + /// + /// Deprecated in favor of [`app_data`](Self::app_data). #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) @@ -138,18 +173,6 @@ where self } - /// Set application level arbitrary data item. - /// - /// Application data stored with `App::app_data()` method is available - /// via `HttpRequest::app_data()` method at runtime. - /// - /// This method could be used for storing `Data` as well, in that case - /// data could be accessed by using `Data` extractor. - pub fn app_data(mut self, ext: U) -> Self { - self.extensions.insert(ext); - self - } - /// Run external configuration as part of the application building /// process /// diff --git a/src/app_service.rs b/src/app_service.rs index a9247b19f..feb9f22a4 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -144,7 +144,9 @@ where } } -/// Service that takes a [`Request`] and delegates to a service that take a [`ServiceRequest`]. +/// The [`Service`] that is passed to `actix-http`'s server builder. +/// +/// Wraps a service receiving a [`ServiceRequest`] into one receiving a [`Request`]. pub struct AppInitService where T: Service, Error = Error>, @@ -275,6 +277,7 @@ impl ServiceFactory for AppRoutingFactory { } } +/// The Actix Web router default entry point. pub struct AppRouting { router: Router, default: HttpService, @@ -299,6 +302,10 @@ impl Service for AppRouting { true }); + // you might expect to find `req.add_data_container()` called here but `HttpRequest` objects + // are created with the root data already set (in `AppInitService::call`) and root data is + // retained when releasing requests back to the pool + if let Some((srv, _info)) = res { srv.call(req) } else { diff --git a/src/config.rs b/src/config.rs index d22bc856e..966141193 100644 --- a/src/config.rs +++ b/src/config.rs @@ -62,6 +62,8 @@ impl AppService { (self.config, self.services) } + /// Clones inner config and default service, returning new `AppService` with empty service list + /// marked as non-root. pub(crate) fn clone_config(&self) -> Self { AppService { config: self.config.clone(), @@ -71,12 +73,12 @@ impl AppService { } } - /// Service configuration + /// Returns reference to configuration. pub fn config(&self) -> &AppConfig { &self.config } - /// Default resource + /// Returns default handler factory. pub fn default_service(&self) -> Rc { self.default.clone() } @@ -116,7 +118,7 @@ impl AppConfig { AppConfig { secure, host, addr } } - /// Needed in actix-test crate. + /// Needed in actix-test crate. Semver exempt. #[doc(hidden)] pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { AppConfig::new(secure, host, addr) @@ -192,6 +194,7 @@ impl ServiceConfig { /// Add shared app data item. /// /// Counterpart to [`App::data()`](crate::App::data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(&mut self, data: U) -> &mut Self { self.app_data(Data::new(data)); self @@ -257,6 +260,8 @@ mod tests { use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{web, App, HttpRequest, HttpResponse}; + // allow deprecated `ServiceConfig::data` + #[allow(deprecated)] #[actix_rt::test] async fn test_data() { let cfg = |cfg: &mut ServiceConfig| { diff --git a/src/data.rs b/src/data.rs index 51db6ce4c..174faba37 100644 --- a/src/data.rs +++ b/src/data.rs @@ -36,6 +36,11 @@ pub(crate) type FnDataFactory = /// If route data is not set for a handler, using `Data` extractor would cause *Internal /// Server Error* response. /// +// TODO: document `dyn T` functionality through converting an Arc +// TODO: note equivalence of req.app_data> and Data extractor +// TODO: note that data must be inserted using Data in order to extract it +/// +/// # Examples /// ``` /// use std::sync::Mutex; /// use actix_web::{web, App, HttpResponse, Responder}; diff --git a/src/resource.rs b/src/resource.rs index 9455895e9..7a4c1248b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -169,41 +169,38 @@ where self } - /// Provide resource specific data. This method allows to add extractor - /// configuration or specific state available via `Data` extractor. - /// Provided data is available for all routes registered for the current resource. - /// Resource data overrides data registered by `App::data()` method. - /// - /// ``` - /// use actix_web::{web, App, FromRequest}; - /// - /// /// extract text data from request - /// async fn index(body: String) -> String { - /// format!("Body {}!", body) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html") - /// // limit size of the payload - /// .data(String::configure(|cfg| { - /// cfg.limit(4096) - /// })) - /// .route( - /// web::get() - /// // register handler - /// .to(index) - /// )); - /// } - /// ``` - #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - /// Add resource data. /// - /// Data of different types from parent contexts will still be accessible. + /// Data of different types from parent contexts will still be accessible. Any `Data` types + /// set here can be extracted in handlers using the `Data` extractor. + /// + /// # Examples + /// ``` + /// use std::cell::Cell; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; + /// + /// struct MyData { + /// count: std::cell::Cell, + /// } + /// + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) + /// } + /// + /// let app = App::new().service( + /// web::resource("/") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route(web::get().to(handler)) + /// ); + /// ``` pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) @@ -212,6 +209,14 @@ where self } + /// Add resource data after wrapping in `Data`. + /// + /// Deprecated in favor of [`app_data`](Self::app_data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) + } + /// Register a new route and add handler. This route matches all requests. /// /// ``` @@ -227,7 +232,6 @@ where /// This is shortcut for: /// /// ``` - /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); @@ -695,7 +699,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::NO_CONTENT); } - // allow deprecated App::data + // allow deprecated `{App, Resource}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_data() { @@ -729,7 +733,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // allow deprecated App::data + // allow deprecated `{App, Resource}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_data_default_service() { diff --git a/src/scope.rs b/src/scope.rs index 86304074b..0caf06ee3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,28 +1,23 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::rc::Rc; +use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; use actix_http::Extensions; use actix_router::{ResourceDef, Router}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, - Transform, + apply, apply_fn_factory, + boxed::{self, BoxService, BoxServiceFactory}, + IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::config::ServiceConfig; -use crate::data::Data; -use crate::dev::{AppService, HttpServiceFactory}; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, +use crate::{ + config::ServiceConfig, + data::Data, + dev::{AppService, HttpServiceFactory}, + guard::Guard, + rmap::ResourceMap, + service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse}, + Error, Resource, Route, }; type Guards = Vec>; @@ -71,16 +66,17 @@ pub struct Scope { impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { - let fref = Rc::new(RefCell::new(None)); + let factory_ref = Rc::new(RefCell::new(None)); + Scope { - endpoint: ScopeEndpoint::new(fref.clone()), + endpoint: ScopeEndpoint::new(Rc::clone(&factory_ref)), rdef: path.to_string(), app_data: None, guards: Vec::new(), services: Vec::new(), default: None, external: Vec::new(), - factory_ref: fref, + factory_ref, } } } @@ -120,40 +116,38 @@ where self } - /// Set or override application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. - /// - /// ``` - /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; - /// - /// struct MyData { - /// counter: Cell, - /// } - /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))) - /// ); - /// } - /// ``` - #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - /// Add scope data. /// - /// Data of different types from parent contexts will still be accessible. + /// Data of different types from parent contexts will still be accessible. Any `Data` types + /// set here can be extracted in handlers using the `Data` extractor. + /// + /// # Examples + /// ``` + /// use std::cell::Cell; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; + /// + /// struct MyData { + /// count: std::cell::Cell, + /// } + /// + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) + /// } + /// + /// let app = App::new().service( + /// web::scope("/app") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route("/", web::get().to(handler)) + /// ); + /// ``` pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) @@ -162,15 +156,20 @@ where self } - /// Run external configuration as part of the scope building - /// process + /// Add scope data after wrapping in `Data`. /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. + /// Deprecated in favor of [`app_data`](Self::app_data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) + } + + /// Run external configuration as part of the scope building process. + /// + /// This function is useful for moving parts of configuration to a different module or library. + /// For example, some of the resource's configuration could be moved to different module. /// /// ``` - /// # extern crate actix_web; /// use actix_web::{web, middleware, App, HttpResponse}; /// /// // this function could be located in different module @@ -191,18 +190,21 @@ where /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// } /// ``` - pub fn configure(mut self, f: F) -> Self + pub fn configure(mut self, cfg_fn: F) -> Self where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); - f(&mut cfg); + cfg_fn(&mut cfg); + self.services.extend(cfg.services); self.external.extend(cfg.external); + // TODO: add Extensions::is_empty check and conditionally insert data self.app_data .get_or_insert_with(Extensions::new) .extend(cfg.app_data); + self } @@ -419,7 +421,7 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); // external resources - for mut rdef in std::mem::take(&mut self.external) { + for mut rdef in mem::take(&mut self.external) { rmap.add(&mut rdef, None); } @@ -991,7 +993,7 @@ mod tests { ); } - // allow deprecated App::data + // allow deprecated {App, Scope}::data #[allow(deprecated)] #[actix_rt::test] async fn test_override_data() { @@ -1011,7 +1013,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // allow deprecated App::data + // allow deprecated `{App, Scope}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_override_data_default_service() { diff --git a/src/service.rs b/src/service.rs index 8d73a87fa..592577467 100644 --- a/src/service.rs +++ b/src/service.rs @@ -17,9 +17,8 @@ use crate::{ dev::insert_slash, guard::Guard, info::ConnectionInfo, - request::HttpRequest, rmap::ResourceMap, - Error, HttpResponse, + Error, HttpRequest, HttpResponse, }; pub trait HttpServiceFactory { diff --git a/src/web.rs b/src/web.rs index 8662848a4..40ac46275 100644 --- a/src/web.rs +++ b/src/web.rs @@ -43,7 +43,6 @@ pub use crate::types::*; /// the exposed `Params` object: /// /// ``` -/// # extern crate actix_web; /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( From 93aa86e30bd2bc664aac80d7f6e02769f9a64750 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:11:01 +0100 Subject: [PATCH 028/861] clippy --- awc/src/request.rs | 7 ++++++- tests/test_error_propagation.rs | 29 ++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index 46dae7fa3..3f312f6e7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -480,6 +480,7 @@ impl ClientRequest { // supported, so we cannot guess Accept-Encoding HTTP header. if slf.response_decompress { // Set Accept-Encoding with compression algorithm awc is built with. + #[allow(clippy::vec_init_then_push)] #[cfg(feature = "__compress")] let accept_encoding = { let mut encoding = vec![]; @@ -496,7 +497,11 @@ impl ClientRequest { #[cfg(feature = "compress-zstd")] encoding.push("zstd"); - assert!(!encoding.is_empty(), "encoding cannot be empty unless __compress feature has been explictily enabled."); + assert!( + !encoding.is_empty(), + "encoding can not be empty unless __compress feature has been explicitly enabled" + ); + encoding.join(", ") }; diff --git a/tests/test_error_propagation.rs b/tests/test_error_propagation.rs index 1b56615a0..3e7320920 100644 --- a/tests/test_error_propagation.rs +++ b/tests/test_error_propagation.rs @@ -1,12 +1,14 @@ -use actix_utils::future::{ok, Ready}; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use actix_web::test::{call_service, init_service, TestRequest}; -use actix_web::{HttpResponse, ResponseError}; -use futures_util::lock::Mutex; -use std::future::Future; -use std::pin::Pin; use std::sync::Arc; -use std::task::{Context, Poll}; + +use actix_utils::future::{ok, Ready}; +use actix_web::{ + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + get, + test::{call_service, init_service, TestRequest}, + ResponseError, +}; +use futures_core::future::LocalBoxFuture; +use futures_util::lock::Mutex; #[derive(Debug, Clone)] pub struct MyError; @@ -19,10 +21,9 @@ impl std::fmt::Display for MyError { } } -#[actix_web::get("/test")] +#[get("/test")] async fn test() -> Result { - Err(MyError)?; - Ok(HttpResponse::NoContent().finish()) + return Err(MyError.into()); } #[derive(Clone)] @@ -62,11 +63,9 @@ where { type Response = ServiceResponse; type Error = actix_web::Error; - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let lock = self.was_error.clone(); From e559a197cc909a6ec9f60900645dfa26a195ca03 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:30:11 +0100 Subject: [PATCH 029/861] remove comment --- src/app_service.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index feb9f22a4..0e590e2b7 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -302,10 +302,6 @@ impl Service for AppRouting { true }); - // you might expect to find `req.add_data_container()` called here but `HttpRequest` objects - // are created with the root data already set (in `AppInitService::call`) and root data is - // retained when releasing requests back to the pool - if let Some((srv, _info)) = res { srv.call(req) } else { From 767e4efe224fe52e66fcb4378a25076ec8beeb9a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 25 Jun 2021 05:53:53 -0400 Subject: [PATCH 030/861] Remove downcast macro from actix-http (#2291) --- actix-http/CHANGES.md | 4 ++ actix-http/src/lib.rs | 3 -- actix-http/src/macros.rs | 110 --------------------------------------- 3 files changed, 4 insertions(+), 113 deletions(-) delete mode 100644 actix-http/src/macros.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c8d65e393..435607463 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,10 @@ ### Changed * Change compression algorithm features flags. [#2250] +### Removed +* `downcast` and `downcast_get_type_id` macros. [#2291] + +[#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 924d5441f..d22e1ee44 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -27,9 +27,6 @@ #[macro_use] extern crate log; -#[macro_use] -mod macros; - pub mod body; mod builder; pub mod client; diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs deleted file mode 100644 index be8e63d6e..000000000 --- a/actix-http/src/macros.rs +++ /dev/null @@ -1,110 +0,0 @@ -#[macro_export] -#[doc(hidden)] -macro_rules! downcast_get_type_id { - () => { - /// A helper method to get the type ID of the type - /// this trait is implemented on. - /// This method is unsafe to *implement*, since `downcast_ref` relies - /// on the returned `TypeId` to perform a cast. - /// - /// Unfortunately, Rust has no notion of a trait method that is - /// unsafe to implement (marking it as `unsafe` makes it unsafe - /// to *call*). As a workaround, we require this method - /// to return a private type along with the `TypeId`. This - /// private type (`PrivateHelper`) has a private constructor, - /// making it impossible for safe code to construct outside of - /// this module. This ensures that safe code cannot violate - /// type-safety by implementing this method. - /// - /// We also take `PrivateHelper` as a parameter, to ensure that - /// safe code cannot obtain a `PrivateHelper` instance by - /// delegating to an existing implementation of `__private_get_type_id__` - #[doc(hidden)] - fn __private_get_type_id__( - &self, - _: PrivateHelper, - ) -> (std::any::TypeId, PrivateHelper) - where - Self: 'static, - { - (std::any::TypeId::of::(), PrivateHelper(())) - } - }; -} - -//Generate implementation for dyn $name -#[doc(hidden)] -#[macro_export] -macro_rules! downcast { - ($name:ident) => { - /// A struct with a private constructor, for use with - /// `__private_get_type_id__`. Its single field is private, - /// ensuring that it can only be constructed from this module - #[doc(hidden)] - pub struct PrivateHelper(()); - - impl dyn $name + 'static { - /// Downcasts generic body to a specific type. - pub fn downcast_ref(&self) -> Option<&T> { - if self.__private_get_type_id__(PrivateHelper(())).0 - == std::any::TypeId::of::() - { - // SAFETY: external crates cannot override the default - // implementation of `__private_get_type_id__`, since - // it requires returning a private type. We can therefore - // rely on the returned `TypeId`, which ensures that this - // case is correct. - unsafe { Some(&*(self as *const dyn $name as *const T)) } - } else { - None - } - } - - /// Downcasts a generic body to a mutable specific type. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - if self.__private_get_type_id__(PrivateHelper(())).0 - == std::any::TypeId::of::() - { - // SAFETY: external crates cannot override the default - // implementation of `__private_get_type_id__`, since - // it requires returning a private type. We can therefore - // rely on the returned `TypeId`, which ensures that this - // case is correct. - unsafe { - Some(&mut *(self as *const dyn $name as *const T as *mut T)) - } - } else { - None - } - } - } - }; -} - -#[cfg(test)] -mod tests { - #![allow(clippy::upper_case_acronyms)] - - trait MB { - downcast_get_type_id!(); - } - - downcast!(MB); - - impl MB for String {} - impl MB for () {} - - #[actix_rt::test] - async fn test_any_casting() { - let mut body = String::from("hello cast"); - let resp_body: &mut dyn MB = &mut body; - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast"); - let body = &mut resp_body.downcast_mut::().unwrap(); - body.push('!'); - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast!"); - let not_body = resp_body.downcast_ref::<()>(); - assert!(not_body.is_none()); - } -} From 2eacb735a4f2f284eeaf80244a119ca89621234d Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 25 Jun 2021 07:25:50 -0400 Subject: [PATCH 031/861] Don't leak internal macros (#2290) --- src/error/macros.rs | 16 ++++------ src/error/mod.rs | 1 + src/error/response_error.rs | 6 ++-- src/http/header/accept.rs | 10 +++--- src/http/header/accept_charset.rs | 4 +-- src/http/header/accept_encoding.rs | 10 +++--- src/http/header/accept_language.rs | 6 ++-- src/http/header/allow.rs | 8 ++--- src/http/header/cache_control.rs | 4 +-- src/http/header/content_language.rs | 6 ++-- src/http/header/content_range.rs | 24 +++++++------- src/http/header/content_type.rs | 4 +-- src/http/header/date.rs | 4 +-- src/http/header/etag.rs | 32 +++++++++---------- src/http/header/expires.rs | 4 +-- src/http/header/if_match.rs | 8 ++--- src/http/header/if_modified_since.rs | 4 +-- src/http/header/if_none_match.rs | 12 +++---- src/http/header/if_range.rs | 6 ++-- src/http/header/if_unmodified_since.rs | 4 +-- src/http/header/last_modified.rs | 4 +-- src/http/header/macros.rs | 44 ++++++++++++-------------- src/http/header/mod.rs | 4 +++ 23 files changed, 113 insertions(+), 112 deletions(-) diff --git a/src/error/macros.rs b/src/error/macros.rs index aeab74308..38650c5e8 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -1,6 +1,4 @@ -#[macro_export] -#[doc(hidden)] -macro_rules! __downcast_get_type_id { +macro_rules! downcast_get_type_id { () => { /// A helper method to get the type ID of the type /// this trait is implemented on. @@ -30,10 +28,8 @@ macro_rules! __downcast_get_type_id { }; } -//Generate implementation for dyn $name -#[doc(hidden)] -#[macro_export] -macro_rules! __downcast_dyn { +// Generate implementation for dyn $name +macro_rules! downcast_dyn { ($name:ident) => { /// A struct with a private constructor, for use with /// `__private_get_type_id__`. Its single field is private, @@ -80,15 +76,17 @@ macro_rules! __downcast_dyn { }; } +pub(crate) use {downcast_dyn, downcast_get_type_id}; + #[cfg(test)] mod tests { #![allow(clippy::upper_case_acronyms)] trait MB { - __downcast_get_type_id!(); + downcast_get_type_id!(); } - __downcast_dyn!(MB); + downcast_dyn!(MB); impl MB for String {} impl MB for () {} diff --git a/src/error/mod.rs b/src/error/mod.rs index 637d6ff16..3ccd5bba6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -18,6 +18,7 @@ mod response_error; pub use self::error::Error; pub use self::internal::*; pub use self::response_error::ResponseError; +pub(crate) use macros::{downcast_dyn, downcast_get_type_id}; /// A convenience [`Result`](std::result::Result) for Actix Web operations. /// diff --git a/src/error/response_error.rs b/src/error/response_error.rs index c58fff8be..41cf20eba 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -9,7 +9,7 @@ use std::{ use actix_http::{body::AnyBody, header, Response, StatusCode}; use bytes::BytesMut; -use crate::{__downcast_dyn, __downcast_get_type_id}; +use crate::error::{downcast_dyn, downcast_get_type_id}; use crate::{helpers, HttpResponse}; /// Errors that can generate responses. @@ -41,10 +41,10 @@ pub trait ResponseError: fmt::Debug + fmt::Display { res.set_body(AnyBody::from(buf)) } - __downcast_get_type_id!(); + downcast_get_type_id!(); } -__downcast_dyn!(ResponseError); +downcast_dyn!(ResponseError); impl ResponseError for Box {} diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 1b6a963da..75366dfae 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -5,7 +5,7 @@ use mime::Mime; use super::{qitem, QualityItem}; use crate::http::header; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify @@ -81,14 +81,14 @@ crate::__define_common_header! { test_accept { // Tests from the RFC - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"audio/*; q=0.2, audio/basic"], Some(Accept(vec![ QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(Accept(vec![ @@ -100,13 +100,13 @@ crate::__define_common_header! { qitem("text/x-c".parse().unwrap()), ]))); // Custom tests - crate::__common_header_test!( + crate::http::header::common_header_test!( test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ qitem(mime::TEXT_PLAIN_UTF_8), ]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index 2c6a0b9f6..bb7d86516 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,6 +1,6 @@ use super::{Charset, QualityItem, ACCEPT_CHARSET}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -57,6 +57,6 @@ crate::__define_common_header! { test_accept_charset { // Test case from RFC - crate::__common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + crate::http::header::common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 734a435b3..cfd29bf77 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -64,12 +64,12 @@ header! { test_accept_encoding { // From the RFC - crate::__common_header_test!(test1, vec![b"compress, gzip"]); - crate::__common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - crate::__common_header_test!(test3, vec![b"*"]); + crate::http::header::common_header_test!(test1, vec![b"compress, gzip"]); + crate::http::header::common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + crate::http::header::common_header_test!(test3, vec![b"*"]); // Note: Removed quality 1 from gzip - crate::__common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); + crate::http::header::common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip - crate::__common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + crate::http::header::common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 034946d4d..1552f6578 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -2,7 +2,7 @@ use language_tags::LanguageTag; use super::{QualityItem, ACCEPT_LANGUAGE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -53,9 +53,9 @@ crate::__define_common_header! { test_accept_language { // From the RFC - crate::__common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + crate::http::header::common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); // Own test - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ qitem("en-US".parse().unwrap()), diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index 15a627b8f..946f70e0a 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ use crate::http::header; use actix_http::http::Method; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as @@ -49,12 +49,12 @@ crate::__define_common_header! { test_allow { // From the RFC - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"GET, HEAD, PUT"], Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); // Own tests - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], Some(HeaderField(vec![ @@ -67,7 +67,7 @@ crate::__define_common_header! { Method::TRACE, Method::CONNECT, Method::PATCH]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test3, vec![b""], Some(HeaderField(Vec::::new()))); diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 620c576ae..05903e3a3 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -49,9 +49,9 @@ use crate::http::header; #[derive(PartialEq, Clone, Debug)] pub struct CacheControl(pub Vec); -crate::__common_header_deref!(CacheControl => Vec); +crate::http::header::common_header_deref!(CacheControl => Vec); -// TODO: this could just be the __define_common_header! macro +// TODO: this could just be the crate::http::header::common_header! macro impl Header for CacheControl { fn name() -> header::HeaderName { header::CACHE_CONTROL diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index c2469edd1..604ada83c 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -50,7 +50,7 @@ crate::__define_common_header! { (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ test_content_language { - crate::__common_header_test!(test1, vec![b"da"]); - crate::__common_header_test!(test2, vec![b"mi, en"]); + crate::http::header::common_header_test!(test1, vec![b"da"]); + crate::http::header::common_header_test!(test2, vec![b"mi, en"]); } } diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index ba0d51742..3bdead2c0 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -4,65 +4,65 @@ use std::str::FromStr; use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] test_content_range { - crate::__common_header_test!(test_bytes, + crate::http::header::common_header_test!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: Some(500) }))); - crate::__common_header_test!(test_bytes_unknown_len, + crate::http::header::common_header_test!(test_bytes_unknown_len, vec![b"bytes 0-499/*"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: None }))); - crate::__common_header_test!(test_bytes_unknown_range, + crate::http::header::common_header_test!(test_bytes_unknown_range, vec![b"bytes */500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, instance_length: Some(500) }))); - crate::__common_header_test!(test_unregistered, + crate::http::header::common_header_test!(test_unregistered, vec![b"seconds 1-2"], Some(ContentRange(ContentRangeSpec::Unregistered { unit: "seconds".to_owned(), resp: "1-2".to_owned() }))); - crate::__common_header_test!(test_no_len, + crate::http::header::common_header_test!(test_no_len, vec![b"bytes 0-499"], None::); - crate::__common_header_test!(test_only_unit, + crate::http::header::common_header_test!(test_only_unit, vec![b"bytes"], None::); - crate::__common_header_test!(test_end_less_than_start, + crate::http::header::common_header_test!(test_end_less_than_start, vec![b"bytes 499-0/500"], None::); - crate::__common_header_test!(test_blank, + crate::http::header::common_header_test!(test_blank, vec![b""], None::); - crate::__common_header_test!(test_bytes_many_spaces, + crate::http::header::common_header_test!(test_bytes_many_spaces, vec![b"bytes 1-2/500 3"], None::); - crate::__common_header_test!(test_bytes_many_slashes, + crate::http::header::common_header_test!(test_bytes_many_slashes, vec![b"bytes 1-2/500/600"], None::); - crate::__common_header_test!(test_bytes_many_dashes, + crate::http::header::common_header_test!(test_bytes_many_dashes, vec![b"bytes 1-2-3/500"], None::); diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index 65cb2a986..e1c419c22 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ use super::CONTENT_TYPE; use mime::Mime; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -52,7 +52,7 @@ crate::__define_common_header! { (ContentType, CONTENT_TYPE) => [Mime] test_content_type { - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"text/html"], Some(HeaderField(mime::TEXT_HTML))); diff --git a/src/http/header/date.rs b/src/http/header/date.rs index 982a1455c..4d1717886 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ use super::{HttpDate, DATE}; use std::time::SystemTime; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the @@ -32,7 +32,7 @@ crate::__define_common_header! { (Date, DATE) => [HttpDate] test_date { - crate::__common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); } } diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index b121fe26f..aded72665 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ use super::{EntityTag, ETAG}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag @@ -50,50 +50,50 @@ crate::__define_common_header! { test_etag { // From the RFC - crate::__common_header_test!(test1, + crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""], Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - crate::__common_header_test!(test2, + crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""], Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - crate::__common_header_test!(test3, + crate::http::header::common_header_test!(test3, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); // Own tests - crate::__common_header_test!(test4, + crate::http::header::common_header_test!(test4, vec![b"\"foobar\""], Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - crate::__common_header_test!(test5, + crate::http::header::common_header_test!(test5, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); - crate::__common_header_test!(test6, + crate::http::header::common_header_test!(test6, vec![b"W/\"weak-etag\""], Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - crate::__common_header_test!(test7, + crate::http::header::common_header_test!(test7, vec![b"W/\"\x65\x62\""], Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - crate::__common_header_test!(test8, + crate::http::header::common_header_test!(test8, vec![b"W/\"\""], Some(ETag(EntityTag::new(true, "".to_owned())))); - crate::__common_header_test!(test9, + crate::http::header::common_header_test!(test9, vec![b"no-dquotes"], None::); - crate::__common_header_test!(test10, + crate::http::header::common_header_test!(test10, vec![b"w/\"the-first-w-is-case-sensitive\""], None::); - crate::__common_header_test!(test11, + crate::http::header::common_header_test!(test11, vec![b""], None::); - crate::__common_header_test!(test12, + crate::http::header::common_header_test!(test12, vec![b"\"unmatched-dquotes1"], None::); - crate::__common_header_test!(test13, + crate::http::header::common_header_test!(test13, vec![b"unmatched-dquotes2\""], None::); - crate::__common_header_test!(test14, + crate::http::header::common_header_test!(test14, vec![b"matched-\"dquotes\""], None::); - crate::__common_header_test!(test15, + crate::http::header::common_header_test!(test15, vec![b"\""], None::); } diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 759e7d280..e810fe267 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ use super::{HttpDate, EXPIRES}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the @@ -36,6 +36,6 @@ crate::__define_common_header! { test_expires { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); } } diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index d4402715d..87a94a809 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_MATCH}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -53,18 +53,18 @@ crate::__define_common_header! { (IfMatch, IF_MATCH) => {Any / (EntityTag)+} test_if_match { - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"\"xyzzy\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned())]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned()), EntityTag::new(false, "r2d2xxxx".to_owned()), EntityTag::new(false, "c3piozzzz".to_owned())]))); - crate::__common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); + crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); } } diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index ba393032d..254003523 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_MODIFIED_SINCE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -36,6 +36,6 @@ crate::__define_common_header! { test_if_modified_since { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index f16b196cc..e1422bd36 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_NONE_MATCH}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -55,11 +55,11 @@ crate::__define_common_header! { (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} test_if_none_match { - crate::__common_header_test!(test1, vec![b"\"xyzzy\""]); - crate::__common_header_test!(test2, vec![b"W/\"xyzzy\""]); - crate::__common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - crate::__common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - crate::__common_header_test!(test5, vec![b"*"]); + crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""]); + crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""]); + crate::http::header::common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + crate::http::header::common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + crate::http::header::common_header_test!(test5, vec![b"*"]); } } diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 9612405e8..cf69e7269 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -113,7 +113,7 @@ mod test_if_range { use crate::http::header::*; use std::str; - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - crate::__common_header_test!(test2, vec![b"\"abc\""]); - crate::__common_header_test!(test3, vec![b"this-is-invalid"], None::); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test2, vec![b"\"abc\""]); + crate::http::header::common_header_test!(test3, vec![b"this-is-invalid"], None::); } diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 26b16b513..1cc7b304e 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_UNMODIFIED_SINCE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -37,6 +37,6 @@ crate::__define_common_header! { test_if_unmodified_since { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index 0de2fc06b..c43bf3ac9 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ use super::{HttpDate, LAST_MODIFIED}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -36,6 +36,6 @@ crate::__define_common_header! { test_last_modified { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 1718a8663..419d4fb6e 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -1,6 +1,4 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_deref { +macro_rules! common_header_deref { ($from:ty => $to:ty) => { impl ::std::ops::Deref for $from { type Target = $to; @@ -20,9 +18,7 @@ macro_rules! __common_header_deref { }; } -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_test_module { +macro_rules! common_header_test_module { ($id:ident, $tm:ident{$($tf:item)*}) => { #[allow(unused_imports)] #[cfg(test)] @@ -37,9 +33,8 @@ macro_rules! __common_header_test_module { } } -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_test { +#[cfg(test)] +macro_rules! common_header_test { ($id:ident, $raw:expr) => { #[test] fn $id() { @@ -99,9 +94,7 @@ macro_rules! __common_header_test { }; } -#[doc(hidden)] -#[macro_export] -macro_rules! __define_common_header { +macro_rules! common_header { // $a:meta: Attributes associated with the header item (usually docs) // $id:ident: Identifier of the header // $n:expr: Lowercase name of the header @@ -112,7 +105,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub Vec<$item>); - crate::__common_header_deref!($id => Vec<$item>); + crate::http::header::common_header_deref!($id => Vec<$item>); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -148,7 +141,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub Vec<$item>); - crate::__common_header_deref!($id => Vec<$item>); + crate::http::header::common_header_deref!($id => Vec<$item>); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -184,7 +177,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub $value); - crate::__common_header_deref!($id => $value); + crate::http::header::common_header_deref!($id => $value); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -267,34 +260,39 @@ macro_rules! __define_common_header { // optional test module ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => ($item)* } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $n) => ($item)+ } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => [$item] } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => {Any / ($item)+} } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; } + +pub(crate) use {common_header, common_header_deref, common_header_test_module}; + +#[cfg(test)] +pub(crate) use common_header_test; diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 0e5651a77..79ba5772b 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -84,4 +84,8 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; + mod macros; +#[cfg(test)] +pub(crate) use macros::common_header_test; +pub(crate) use macros::{common_header, common_header_deref, common_header_test_module}; From 9a263933757ab6e0bd1a6a688b8bce04584e7dfe Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Fri, 25 Jun 2021 15:27:22 +0400 Subject: [PATCH 032/861] Remove duplicated step from CI workflow (#2289) --- .github/workflows/ci.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be595e35c..8bc04dbd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,17 +44,11 @@ jobs: profile: minimal override: true - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} - profile: minimal - override: true - - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: command: generate-lockfile + - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 @@ -102,6 +96,7 @@ jobs: run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml --verbose + - name: Upload to Codecov if: > matrix.target.os == 'ubuntu-latest' From 5a480d1d789fc55e9c26434f4294d8ac13eea1bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Jun 2021 12:28:04 +0100 Subject: [PATCH 033/861] re-add serde error impls --- src/error/response_error.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/error/response_error.rs b/src/error/response_error.rs index 41cf20eba..c3c543419 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -57,6 +57,10 @@ impl ResponseError for serde::de::value::Error { } } +impl ResponseError for serde_json::Error {} + +impl ResponseError for serde_urlencoded::ser::Error {} + impl ResponseError for std::str::Utf8Error { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST From 539697292adabb0f682004f47d81b9ccd9eba121 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Jun 2021 13:19:42 +0100 Subject: [PATCH 034/861] fix scope and resource middleware data access (#2288) --- CHANGES.md | 4 ++ src/app.rs | 7 ++-- src/app_service.rs | 26 ++++++------- src/config.rs | 2 +- src/resource.rs | 95 +++++++++++++++++++++++++++------------------- src/scope.rs | 83 ++++++++++++++++++++++++++++------------ 6 files changed, 136 insertions(+), 81 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 876e1c03d..3da742f9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,11 +11,15 @@ * Change compression algorithm features flags. [#2250] * Deprecate `App::data` and `App::data_factory`. [#2271] +### Fixed +* Scope and Resource middleware can access data items set on their own layer. [#2288] + [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 +[#2288]: https://github.com/actix/actix-web/pull/2288 ## 4.0.0-beta.7 - 2021-06-17 diff --git a/src/app.rs b/src/app.rs index 677f73805..5cff20568 100644 --- a/src/app.rs +++ b/src/app.rs @@ -43,13 +43,14 @@ impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { - let fref = Rc::new(RefCell::new(None)); + let factory_ref = Rc::new(RefCell::new(None)); + App { - endpoint: AppEntry::new(fref.clone()), + endpoint: AppEntry::new(factory_ref.clone()), data_factories: Vec::new(), services: Vec::new(), default: None, - factory_ref: fref, + factory_ref, external: Vec::new(), extensions: Extensions::new(), _phantom: PhantomData, diff --git a/src/app_service.rs b/src/app_service.rs index 0e590e2b7..bdb7ec433 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,22 +1,22 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::{cell::RefCell, mem, rc::Rc}; use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Router, Url}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{fn_service, Service, ServiceFactory}; +use actix_service::{ + boxed::{self, BoxService, BoxServiceFactory}, + fn_service, Service, ServiceFactory, +}; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::data::FnDataFactory; -use crate::error::Error; -use crate::guard::Guard; -use crate::request::{HttpRequest, HttpRequestPool}; -use crate::rmap::ResourceMap; -use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; use crate::{ config::{AppConfig, AppService}, - HttpResponse, + data::FnDataFactory, + guard::Guard, + request::{HttpRequest, HttpRequestPool}, + rmap::ResourceMap, + service::{AppServiceFactory, ServiceRequest, ServiceResponse}, + Error, HttpResponse, }; type Guards = Vec>; @@ -75,7 +75,7 @@ where let mut config = AppService::new(config, default.clone()); // register services - std::mem::take(&mut *self.services.borrow_mut()) + mem::take(&mut *self.services.borrow_mut()) .into_iter() .for_each(|mut srv| srv.register(&mut config)); @@ -98,7 +98,7 @@ where }); // external resources - for mut rdef in std::mem::take(&mut *self.external.borrow_mut()) { + for mut rdef in mem::take(&mut *self.external.borrow_mut()) { rmap.add(&mut rdef, None); } diff --git a/src/config.rs b/src/config.rs index 966141193..884128308 100644 --- a/src/config.rs +++ b/src/config.rs @@ -94,9 +94,9 @@ impl AppService { F: IntoServiceFactory, S: ServiceFactory< ServiceRequest, - Config = (), Response = ServiceResponse, Error = Error, + Config = (), InitError = (), > + 'static, { diff --git a/src/resource.rs b/src/resource.rs index 7a4c1248b..9f5cf3cb2 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -400,34 +400,28 @@ where *rdef.name_mut() = name.clone(); } - config.register_service(rdef, guards, self, None) - } -} - -impl IntoServiceFactory for Resource -where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - fn into_factory(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, - app_data: self.app_data.map(Rc::new), default: self.default, }); - self.endpoint + let resource_data = self.app_data.map(Rc::new); + + // wraps endpoint service (including middleware) call and injects app data for this scope + let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| { + if let Some(ref data) = resource_data { + req.add_data_container(Rc::clone(data)); + } + + srv.call(req) + }); + + config.register_service(rdef, guards, endpoint, None) } } pub struct ResourceFactory { routes: Vec, - app_data: Option>, default: HttpNewService, } @@ -446,8 +440,6 @@ impl ServiceFactory for ResourceFactory { // construct route service factory futures let factory_fut = join_all(self.routes.iter().map(|route| route.new_service(()))); - let app_data = self.app_data.clone(); - Box::pin(async move { let default = default_fut.await?; let routes = factory_fut @@ -455,18 +447,13 @@ impl ServiceFactory for ResourceFactory { .into_iter() .collect::, _>>()?; - Ok(ResourceService { - routes, - app_data, - default, - }) + Ok(ResourceService { routes, default }) }) } } pub struct ResourceService { routes: Vec, - app_data: Option>, default: HttpService, } @@ -480,18 +467,10 @@ impl Service for ResourceService { 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) } } @@ -528,11 +507,14 @@ mod tests { use actix_service::Service; use actix_utils::future::ok; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{guard, web, App, Error, HttpResponse}; + use crate::{ + guard, + http::{header, HeaderValue, Method, StatusCode}, + middleware::DefaultHeaders, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, TestRequest}, + web, App, Error, HttpMessage, HttpResponse, + }; #[actix_rt::test] async fn test_middleware() { @@ -753,4 +735,39 @@ mod tests { let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn test_middleware_app_data() { + let srv = init_service( + App::new().service( + web::resource("test") + .app_data(1usize) + .wrap_fn(|req, srv| { + assert_eq!(req.app_data::(), Some(&1usize)); + req.extensions_mut().insert(1usize); + srv.call(req) + }) + .route(web::get().to(HttpResponse::Ok)) + .default_service(|req: ServiceRequest| async move { + let (req, _) = req.into_parts(); + + assert_eq!(req.extensions().get::(), Some(&1)); + + Ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().finish(), + )) + }), + ), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::post().uri("/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } diff --git a/src/scope.rs b/src/scope.rs index 0caf06ee3..aa546c422 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -427,7 +427,6 @@ where // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { - app_data: self.app_data.take().map(Rc::new), default, services: cfg .into_services() @@ -449,18 +448,28 @@ where Some(self.guards) }; + let scope_data = self.app_data.map(Rc::new); + + // wraps endpoint service (including middleware) call and injects app data for this scope + let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| { + if let Some(ref data) = scope_data { + req.add_data_container(Rc::clone(data)); + } + + srv.call(req) + }); + // register final service config.register_service( ResourceDef::root_prefix(&self.rdef), guards, - self.endpoint, + endpoint, Some(Rc::new(rmap)), ) } } pub struct ScopeFactory { - app_data: Option>, services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, default: Rc, } @@ -488,8 +497,6 @@ impl ServiceFactory for ScopeFactory { } })); - let app_data = self.app_data.clone(); - Box::pin(async move { let default = default_fut.await?; @@ -505,17 +512,12 @@ impl ServiceFactory for ScopeFactory { }) .finish(); - Ok(ScopeService { - app_data, - router, - default, - }) + Ok(ScopeService { router, default }) }) } } pub struct ScopeService { - app_data: Option>, router: Router>>, default: HttpService, } @@ -539,10 +541,6 @@ impl Service for ScopeService { true }); - if let Some(ref app_data) = self.app_data { - req.add_data_container(app_data.clone()); - } - if let Some((srv, _info)) = res { srv.call(req) } else { @@ -581,12 +579,15 @@ mod tests { use actix_utils::future::ok; use bytes::Bytes; - use crate::dev::Body; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{guard, web, App, HttpRequest, HttpResponse}; + use crate::{ + dev::Body, + guard, + http::{header, HeaderValue, Method, StatusCode}, + middleware::DefaultHeaders, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, read_body, TestRequest}, + web, App, HttpMessage, HttpRequest, HttpResponse, + }; #[actix_rt::test] async fn test_scope() { @@ -918,10 +919,7 @@ mod tests { async fn test_default_resource_propagation() { let srv = init_service( App::new() - .service( - web::scope("/app1") - .default_service(web::resource("").to(HttpResponse::BadRequest)), - ) + .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest))) .service(web::scope("/app2")) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::MethodNotAllowed())) @@ -993,6 +991,41 @@ mod tests { ); } + #[actix_rt::test] + async fn test_middleware_app_data() { + let srv = init_service( + App::new().service( + web::scope("app") + .app_data(1usize) + .wrap_fn(|req, srv| { + assert_eq!(req.app_data::(), Some(&1usize)); + req.extensions_mut().insert(1usize); + srv.call(req) + }) + .route("/test", web::get().to(HttpResponse::Ok)) + .default_service(|req: ServiceRequest| async move { + let (req, _) = req.into_parts(); + + assert_eq!(req.extensions().get::(), Some(&1)); + + Ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().finish(), + )) + }), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/default").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + // allow deprecated {App, Scope}::data #[allow(deprecated)] #[actix_rt::test] From 09afd033fce961db3f46e4aabdaefe85e99fb5d2 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 25 Jun 2021 16:21:57 +0300 Subject: [PATCH 035/861] files: file path filtering closure (#2274) Co-authored-by: Rob Ede --- actix-files/CHANGES.md | 3 +++ actix-files/src/files.rs | 50 +++++++++++++++++++++++++++++++++++--- actix-files/src/lib.rs | 41 ++++++++++++++++++++++++++++++- actix-files/src/service.rs | 15 +++++++++++- 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index bec67dd4e..54b0344a4 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Added `Files::path_filter()`. [#2274] + +[#2274]: https://github.com/actix/actix-web/pull/2274 ## 0.6.0-beta.5 - 2021-06-17 diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index c48cf59a7..49d81eb03 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,9 +1,17 @@ -use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; +use std::{ + cell::RefCell, + fmt, io, + path::{Path, PathBuf}, + rc::Rc, +}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::{ - dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, + dev::{ + AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, + ServiceResponse, + }, error::Error, guard::Guard, http::header::DispositionType, @@ -13,7 +21,7 @@ use futures_core::future::LocalBoxFuture; use crate::{ directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, - MimeOverride, + MimeOverride, PathFilter, }; /// Static files handling service. @@ -36,6 +44,7 @@ pub struct Files { default: Rc>>>, renderer: Rc, mime_override: Option>, + path_filter: Option>, file_flags: named::Flags, use_guards: Option>, guards: Vec>, @@ -60,6 +69,7 @@ impl Clone for Files { file_flags: self.file_flags, path: self.path.clone(), mime_override: self.mime_override.clone(), + path_filter: self.path_filter.clone(), use_guards: self.use_guards.clone(), guards: self.guards.clone(), hidden_files: self.hidden_files, @@ -104,6 +114,7 @@ impl Files { default: Rc::new(RefCell::new(None)), renderer: Rc::new(directory_listing), mime_override: None, + path_filter: None, file_flags: named::Flags::default(), use_guards: None, guards: Vec::new(), @@ -149,6 +160,38 @@ impl Files { self } + /// Sets path filtering closure. + /// + /// The path provided to the closure is relative to `serve_from` path. + /// You can safely join this path with the `serve_from` path to get the real path. + /// However, the real path may not exist since the filter is called before checking path existence. + /// + /// When a path doesn't pass the filter, [`Files::default_handler`] is called if set, otherwise, + /// `404 Not Found` is returned. + /// + /// # Examples + /// ``` + /// use std::path::Path; + /// use actix_files::Files; + /// + /// // prevent searching subdirectories and following symlinks + /// let files_service = Files::new("/", "./static").path_filter(|path, _| { + /// path.components().count() == 1 + /// && Path::new("./static") + /// .join(path) + /// .symlink_metadata() + /// .map(|m| !m.file_type().is_symlink()) + /// .unwrap_or(false) + /// }); + /// ``` + pub fn path_filter(mut self, f: F) -> Self + where + F: Fn(&Path, &RequestHead) -> bool + 'static, + { + self.path_filter = Some(Rc::new(f)); + self + } + /// Set index file /// /// Shows specific index file for directories instead of @@ -318,6 +361,7 @@ impl ServiceFactory for Files { default: None, renderer: self.renderer.clone(), mime_override: self.mime_override.clone(), + path_filter: self.path_filter.clone(), file_flags: self.file_flags, guards: self.use_guards.clone(), hidden_files: self.hidden_files, diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index c9cc79193..1eb091aaf 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -16,11 +16,12 @@ use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ - dev::{ServiceRequest, ServiceResponse}, + dev::{RequestHead, ServiceRequest, ServiceResponse}, error::Error, http::header::DispositionType, }; use mime_guess::from_ext; +use std::path::Path; mod chunked; mod directory; @@ -56,6 +57,8 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime { type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType; +type PathFilter = dyn Fn(&Path, &RequestHead) -> bool; + #[cfg(test)] mod tests { use std::{ @@ -901,4 +904,40 @@ mod tests { let bytes = test::read_body(resp).await; assert!(format!("{:?}", bytes).contains("/tests/test.png")); } + + #[actix_rt::test] + async fn test_path_filter() { + // prevent searching subdirectories + let st = Files::new("/", ".") + .path_filter(|path, _| path.components().count() == 1) + .new_service(()) + .await + .unwrap(); + + let req = TestRequest::with_uri("/Cargo.toml").to_srv_request(); + let resp = test::call_service(&st, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/src/lib.rs").to_srv_request(); + let resp = test::call_service(&st, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_default_handler_filter() { + let st = Files::new("/", ".") + .default_handler(|req: ServiceRequest| { + ok(req.into_response(HttpResponse::Ok().body("default content"))) + }) + .path_filter(|path, _| path.extension() == Some("png".as_ref())) + .new_service(()) + .await + .unwrap(); + let req = TestRequest::with_uri("/Cargo.toml").to_srv_request(); + 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")); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 64938e5ef..09122c63e 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -13,7 +13,7 @@ use futures_core::future::LocalBoxFuture; use crate::{ named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, - PathBufWrap, + PathBufWrap, PathFilter, }; /// Assembled file serving service. @@ -25,6 +25,7 @@ pub struct FilesService { pub(crate) default: Option, pub(crate) renderer: Rc, pub(crate) mime_override: Option>, + pub(crate) path_filter: Option>, pub(crate) file_flags: named::Flags, pub(crate) guards: Option>, pub(crate) hidden_files: bool, @@ -82,6 +83,18 @@ impl Service for FilesService { Err(e) => return Box::pin(ok(req.error_response(e))), }; + if let Some(filter) = &self.path_filter { + if !filter(real_path.as_ref(), req.head()) { + if let Some(ref default) = self.default { + return Box::pin(default.call(req)); + } else { + return Box::pin(ok( + req.into_response(actix_web::HttpResponse::NotFound().finish()) + )); + } + } + } + // full file path let path = self.directory.join(&real_path); if let Err(err) = path.canonicalize() { From 5eba95b731856ab3d1c4fd7f4fb18f5e647d021a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 25 Jun 2021 19:39:06 -0400 Subject: [PATCH 036/861] simplify `ConnectionInfo::new` (#2282) --- .github/workflows/ci.yml | 2 - CHANGES.md | 2 + Cargo.toml | 2 +- src/config.rs | 5 + src/info.rs | 355 +++++++++++++++++++++++++-------------- src/test.rs | 5 + 6 files changed, 245 insertions(+), 126 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bc04dbd7..22b92759a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,6 @@ jobs: uses: actions-rs/cargo@v1 with: command: generate-lockfile - - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 @@ -96,7 +95,6 @@ jobs: run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml --verbose - - name: Upload to Codecov if: > matrix.target.os == 'ubuntu-latest' diff --git a/CHANGES.md b/CHANGES.md index 3da742f9d..b4320d783 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### Changed * Change compression algorithm features flags. [#2250] * Deprecate `App::data` and `App::data_factory`. [#2271] +* Smarter extraction of `ConnectionInfo` parts. [#2282] ### Fixed * Scope and Resource middleware can access data items set on their own layer. [#2288] @@ -19,6 +20,7 @@ [#2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 +[#2282]: https://github.com/actix/actix-web/pull/2282 [#2288]: https://github.com/actix/actix-web/pull/2288 diff --git a/Cargo.toml b/Cargo.toml index 779b52255..293e6bccd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.6", features = ["openssl"] } brotli2 = "0.3.2" -criterion = "0.3" +criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" flate2 = "1.0.13" zstd = "0.7" diff --git a/src/config.rs b/src/config.rs index 884128308..b072ace16 100644 --- a/src/config.rs +++ b/src/config.rs @@ -144,6 +144,11 @@ impl AppConfig { pub fn local_addr(&self) -> SocketAddr { self.addr } + + #[cfg(test)] + pub(crate) fn set_host(&mut self, host: &str) { + self.host = host.to_owned(); + } } impl Default for AppConfig { diff --git a/src/info.rs b/src/info.rs index c6ff54efe..1f8263add 100644 --- a/src/info.rs +++ b/src/info.rs @@ -2,16 +2,35 @@ use std::{cell::Ref, convert::Infallible, net::SocketAddr}; use actix_utils::future::{err, ok, Ready}; use derive_more::{Display, Error}; +use once_cell::sync::Lazy; use crate::{ dev::{AppConfig, Payload, RequestHead}, - http::header::{self, HeaderName}, + http::{ + header::{self, HeaderName}, + uri::{Authority, Scheme}, + }, FromRequest, HttpRequest, ResponseError, }; -const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; -const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; -const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; +static X_FORWARDED_FOR: Lazy = + Lazy::new(|| HeaderName::from_static("x-forwarded-for")); +static X_FORWARDED_HOST: Lazy = + Lazy::new(|| HeaderName::from_static("x-forwarded-host")); +static X_FORWARDED_PROTO: Lazy = + Lazy::new(|| HeaderName::from_static("x-forwarded-proto")); + +/// Trim whitespace then any quote marks. +fn unquote(val: &str) -> &str { + val.trim().trim_start_matches('"').trim_end_matches('"') +} + +/// Extracts and trims first value for given header name. +fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> { + let hdr = req.headers.get(name)?.to_str().ok()?; + let val = hdr.split(',').next()?.trim(); + Some(val) +} /// HTTP connection information. /// @@ -31,6 +50,19 @@ const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; /// } /// # let _svc = actix_web::web::to(handler); /// ``` +/// +/// # Implementation Notes +/// Parses `Forwarded` header information according to [RFC 7239][rfc7239] but does not try to +/// interpret the values for each property. As such, the getter methods on `ConnectionInfo` return +/// strings instead of IP addresses or other types to acknowledge that they may be +/// [obfuscated][rfc7239-63] or [unknown][rfc7239-62]. +/// +/// If the older, related headers are also present (eg. `X-Forwarded-For`), then `Forwarded` +/// is preferred. +/// +/// [rfc7239]: https://datatracker.ietf.org/doc/html/rfc7239 +/// [rfc7239-62]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.2 +/// [rfc7239-63]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.3 #[derive(Debug, Clone, Default)] pub struct ConnectionInfo { scheme: String, @@ -48,105 +80,75 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[allow(clippy::cognitive_complexity, clippy::borrow_interior_mutable_const)] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; let mut realip_remote_addr = None; - // load forwarded header - for hdr in req.headers.get_all(&header::FORWARDED) { - if let Ok(val) = hdr.to_str() { - for pair in val.split(';') { - for el in pair.split(',') { - let mut items = el.trim().splitn(2, '='); - if let Some(name) = items.next() { - if let Some(val) = items.next() { - match &name.to_lowercase() as &str { - "for" => { - if realip_remote_addr.is_none() { - realip_remote_addr = Some(val.trim()); - } - } - "proto" => { - if scheme.is_none() { - scheme = Some(val.trim()); - } - } - "host" => { - if host.is_none() { - host = Some(val.trim()); - } - } - _ => {} - } - } - } - } + for (name, val) in req + .headers + .get_all(&header::FORWARDED) + .into_iter() + .filter_map(|hdr| hdr.to_str().ok()) + // "for=1.2.3.4, for=5.6.7.8; scheme=https" + .flat_map(|val| val.split(';')) + // ["for=1.2.3.4, for=5.6.7.8", " scheme=https"] + .flat_map(|vals| vals.split(',')) + // ["for=1.2.3.4", " for=5.6.7.8", " scheme=https"] + .flat_map(|pair| { + let mut items = pair.trim().splitn(2, '='); + Some((items.next()?, items.next()?)) + }) + { + // [(name , val ), ... ] + // [("for", "1.2.3.4"), ("for", "5.6.7.8"), ("scheme", "https")] + + // taking the first value for each property is correct because spec states that first + // "for" value is client and rest are proxies; multiple values other properties have + // no defined semantics + // + // > In a chain of proxy servers where this is fully utilized, the first + // > "for" parameter will disclose the client where the request was first + // > made, followed by any subsequent proxy identifiers. + // --- https://datatracker.ietf.org/doc/html/rfc7239#section-5.2 + + match name.trim().to_lowercase().as_str() { + "for" => realip_remote_addr.get_or_insert_with(|| unquote(val)), + "proto" => scheme.get_or_insert_with(|| unquote(val)), + "host" => host.get_or_insert_with(|| unquote(val)), + "by" => { + // TODO: implement https://datatracker.ietf.org/doc/html/rfc7239#section-5.1 + continue; } - } + _ => continue, + }; } - // scheme - if scheme.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) - { - if let Ok(h) = h.to_str() { - scheme = h.split(',').next().map(|v| v.trim()); - } - } - if scheme.is_none() { - scheme = req.uri.scheme().map(|a| a.as_str()); - if scheme.is_none() && cfg.secure() { - scheme = Some("https") - } - } - } + let scheme = scheme + .or_else(|| first_header_value(req, &*X_FORWARDED_PROTO)) + .or_else(|| req.uri.scheme().map(Scheme::as_str)) + .or_else(|| Some("https").filter(|_| cfg.secure())) + .unwrap_or("http") + .to_owned(); - // host - if host.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) - { - if let Ok(h) = h.to_str() { - host = h.split(',').next().map(|v| v.trim()); - } - } - if host.is_none() { - if let Some(h) = req.headers.get(&header::HOST) { - host = h.to_str().ok(); - } - if host.is_none() { - host = req.uri.authority().map(|a| a.as_str()); - if host.is_none() { - host = Some(cfg.host()); - } - } - } - } + let host = host + .or_else(|| first_header_value(req, &*X_FORWARDED_HOST)) + .or_else(|| req.headers.get(&header::HOST)?.to_str().ok()) + .or_else(|| req.uri.authority().map(Authority::as_str)) + .unwrap_or(cfg.host()) + .to_owned(); - // get remote_addraddr from socketaddr - let remote_addr = req.peer_addr.map(|addr| format!("{}", addr)); + let realip_remote_addr = realip_remote_addr + .or_else(|| first_header_value(req, &*X_FORWARDED_FOR)) + .map(str::to_owned); - if realip_remote_addr.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) - { - if let Ok(h) = h.to_str() { - realip_remote_addr = h.split(',').next().map(|v| v.trim()); - } - } - } + let remote_addr = req.peer_addr.map(|addr| addr.to_string()); ConnectionInfo { remote_addr, - scheme: scheme.unwrap_or("http").to_owned(), - host: host.unwrap_or("localhost").to_owned(), - realip_remote_addr: realip_remote_addr.map(|s| s.to_owned()), + scheme, + host, + realip_remote_addr, } } @@ -175,19 +177,16 @@ impl ConnectionInfo { &self.host } - /// remote_addr address of the request. + /// Remote address of the connection. /// - /// Get remote_addr address from socket address + /// Get remote_addr address from socket address. pub fn remote_addr(&self) -> Option<&str> { - if let Some(ref remote_addr) = self.remote_addr { - Some(remote_addr) - } else { - None - } + self.remote_addr.as_deref() } - /// Real ip remote addr of client initiated HTTP request. + + /// Real IP (remote address) of client that initiated request. /// - /// The addr is resolved through the following headers, in this order: + /// The address is resolved through the following headers, in this order: /// /// - Forwarded /// - X-Forwarded-For @@ -196,17 +195,14 @@ impl ConnectionInfo { /// # Security /// Do not use this function for security purposes, unless you can ensure the Forwarded and /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket - /// address explicitly, use - /// [`HttpRequest::peer_addr()`](super::web::HttpRequest::peer_addr()) instead. + /// address explicitly, use [`HttpRequest::peer_addr()`][peer_addr] instead. + /// + /// [peer_addr]: crate::web::HttpRequest::peer_addr() #[inline] pub fn realip_remote_addr(&self) -> Option<&str> { - if let Some(ref r) = self.realip_remote_addr { - Some(r) - } else if let Some(ref remote_addr) = self.remote_addr { - Some(remote_addr) - } else { - None - } + self.realip_remote_addr + .as_deref() + .or_else(|| self.remote_addr.as_deref()) } } @@ -274,13 +270,60 @@ mod tests { use super::*; use crate::test::TestRequest; + const X_FORWARDED_FOR: &str = "x-forwarded-for"; + const X_FORWARDED_HOST: &str = "x-forwarded-host"; + const X_FORWARDED_PROTO: &str = "x-forwarded-proto"; + #[test] - fn test_forwarded() { + fn info_default() { let req = TestRequest::default().to_http_request(); let info = req.connection_info(); assert_eq!(info.scheme(), "http"); assert_eq!(info.host(), "localhost:8080"); + } + #[test] + fn host_header() { + let req = TestRequest::default() + .insert_header((header::HOST, "rust-lang.org")) + .to_http_request(); + + let info = req.connection_info(); + assert_eq!(info.scheme(), "http"); + assert_eq!(info.host(), "rust-lang.org"); + assert_eq!(info.realip_remote_addr(), None); + } + + #[test] + fn x_forwarded_for_header() { + let req = TestRequest::default() + .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")); + } + + #[test] + fn x_forwarded_host_header() { + let req = TestRequest::default() + .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); + } + + #[test] + fn x_forwarded_proto_header() { + let req = TestRequest::default() + .insert_header((X_FORWARDED_PROTO, "https")) + .to_http_request(); + let info = req.connection_info(); + assert_eq!(info.scheme(), "https"); + } + + #[test] + fn forwarded_header() { let req = TestRequest::default() .insert_header(( header::FORWARDED, @@ -294,45 +337,111 @@ mod tests { assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); let req = TestRequest::default() - .insert_header((header::HOST, "rust-lang.org")) + .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(); - assert_eq!(info.scheme(), "http"); + assert_eq!(info.scheme(), "https"); assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.realip_remote_addr(), None); + assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); + } + #[test] + fn forwarded_case_sensitivity() { let req = TestRequest::default() - .insert_header((X_FORWARDED_FOR, "192.0.2.60")) + .insert_header((header::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")); + } + #[test] + fn forwarded_weird_whitespace() { let req = TestRequest::default() - .insert_header((X_FORWARDED_HOST, "192.0.2.60")) + .insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https")) .to_http_request(); let info = req.connection_info(); - assert_eq!(info.host(), "192.0.2.60"); - assert_eq!(info.realip_remote_addr(), None); + assert_eq!(info.realip_remote_addr(), Some("1.2.3.4")); + assert_eq!(info.scheme(), "https"); let req = TestRequest::default() - .insert_header((X_FORWARDED_PROTO, "https")) + .insert_header((header::FORWARDED, " for = 1.2.3.4 ")) + .to_http_request(); + let info = req.connection_info(); + assert_eq!(info.realip_remote_addr(), Some("1.2.3.4")); + } + + #[test] + fn forwarded_for_quoted() { + let req = TestRequest::default() + .insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#)) + .to_http_request(); + let info = req.connection_info(); + assert_eq!(info.realip_remote_addr(), Some("192.0.2.60:8080")); + } + + #[test] + fn forwarded_for_ipv6() { + let req = TestRequest::default() + .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#)) + .to_http_request(); + let info = req.connection_info(); + assert_eq!(info.realip_remote_addr(), Some("[2001:db8:cafe::17]:4711")); + } + + #[test] + fn forwarded_for_multiple() { + let req = TestRequest::default() + .insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17")) + .to_http_request(); + let info = req.connection_info(); + // takes the first value + assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); + } + + #[test] + fn scheme_from_uri() { + let req = TestRequest::get() + .uri("https://actix.rs/test") .to_http_request(); let info = req.connection_info(); assert_eq!(info.scheme(), "https"); } - #[actix_rt::test] - async fn test_conn_info() { - let req = TestRequest::default() - .uri("http://actix.rs/") + #[test] + fn host_from_uri() { + let req = TestRequest::get() + .uri("https://actix.rs/test") .to_http_request(); - let conn_info = ConnectionInfo::extract(&req).await.unwrap(); - assert_eq!(conn_info.scheme(), "http"); + let info = req.connection_info(); + assert_eq!(info.host(), "actix.rs"); + } + + #[test] + fn host_from_server_hostname() { + let mut req = TestRequest::get(); + req.set_server_hostname("actix.rs"); + let req = req.to_http_request(); + + let info = req.connection_info(); + assert_eq!(info.host(), "actix.rs"); } #[actix_rt::test] - async fn test_peer_addr() { + async fn conn_info_extract() { + let req = TestRequest::default() + .uri("https://actix.rs/test") + .to_http_request(); + let conn_info = ConnectionInfo::extract(&req).await.unwrap(); + assert_eq!(conn_info.scheme(), "https"); + assert_eq!(conn_info.host(), "actix.rs"); + } + + #[actix_rt::test] + async fn peer_addr_extract() { let addr = "127.0.0.1:8080".parse().unwrap(); let req = TestRequest::default().peer_addr(addr).to_http_request(); let peer_addr = PeerAddr::extract(&req).await.unwrap(); diff --git a/src/test.rs b/src/test.rs index 05a4ba7f2..634826d19 100644 --- a/src/test.rs +++ b/src/test.rs @@ -613,6 +613,11 @@ impl TestRequest { let req = self.to_request(); call_service(app, req).await } + + #[cfg(test)] + pub fn set_server_hostname(&mut self, host: &str) { + self.config.set_host(host) + } } #[cfg(test)] From 262c6bc828d58a4acf0d1c8b291f5a6c6b3f82fd Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sat, 26 Jun 2021 18:33:43 +0400 Subject: [PATCH 037/861] Various refactorings (#2281) Co-authored-by: Rob Ede --- .gitignore | 3 ++ CHANGES.md | 1 + actix-files/src/named.rs | 8 +++--- actix-http/benches/uninit-headers.rs | 6 ++-- actix-http/src/client/connector.rs | 8 ++---- actix-http/src/client/h2proto.rs | 13 ++++----- actix-http/src/config.rs | 8 +++--- actix-http/src/error.rs | 2 +- actix-http/src/h1/decoder.rs | 6 ++-- actix-http/src/h1/dispatcher.rs | 3 +- actix-http/src/h1/payload.rs | 6 ++-- actix-http/src/header/map.rs | 6 ++-- actix-http/src/message.rs | 27 +++++++++--------- actix-web-codegen/src/route.rs | 7 ++--- awc/src/request.rs | 4 ++- awc/src/ws.rs | 2 +- benches/responder.rs | 11 ++++---- benches/service.rs | 6 ++-- src/app_service.rs | 4 +-- src/http/header/content_disposition.rs | 18 ++++-------- src/middleware/compress.rs | 3 +- src/request.rs | 6 +--- src/resource.rs | 2 +- src/response/builder.rs | 2 +- src/server.rs | 39 ++++++++++++-------------- src/service.rs | 6 +--- src/types/form.rs | 3 +- src/types/path.rs | 3 +- src/types/payload.rs | 3 +- src/types/query.rs | 3 +- 30 files changed, 98 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index 638a4397a..543403267 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ guide/build/ # Configuration directory generated by CLion .idea + +# Configuration directory generated by VSCode +.vscode diff --git a/CHANGES.md b/CHANGES.md index b4320d783..f6a0b28c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ * Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] + ### Fixed * Scope and Resource middleware can access data items set on their own layer. [#2288] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 37f8def3e..241e78cf0 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -355,8 +355,8 @@ impl NamedFile { } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = (last_modified, req.get_header()) { - let t1: SystemTime = m.clone().into(); - let t2: SystemTime = since.clone().into(); + let t1: SystemTime = (*m).into(); + let t2: SystemTime = (*since).into(); match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { (Ok(t1), Ok(t2)) => t1.as_secs() > t2.as_secs(), @@ -374,8 +374,8 @@ impl NamedFile { } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) { - let t1: SystemTime = m.clone().into(); - let t2: SystemTime = since.clone().into(); + let t1: SystemTime = (*m).into(); + let t2: SystemTime = (*since).into(); match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { (Ok(t1), Ok(t2)) => t1.as_secs() <= t2.as_secs(), diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 83e74171c..53a2528ab 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -78,12 +78,12 @@ impl HeaderIndex { // test cases taken from: // https://github.com/seanmonstar/httparse/blob/master/benches/parse.rs -const REQ_SHORT: &'static [u8] = b"\ +const REQ_SHORT: &[u8] = b"\ GET / HTTP/1.0\r\n\ Host: example.com\r\n\ Cookie: session=60; user_id=1\r\n\r\n"; -const REQ: &'static [u8] = b"\ +const REQ: &[u8] = b"\ GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\ Host: www.kittyhell.com\r\n\ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\ @@ -119,6 +119,8 @@ mod _original { use std::mem::MaybeUninit; pub fn parse_headers(src: &mut BytesMut) -> usize { + #![allow(clippy::uninit_assumed_init)] + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 508fe748b..bd46919e8 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -85,7 +85,7 @@ impl Connector<()> { use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); - for proto in protocols.iter() { + for proto in &protocols { alpn.put_u8(proto.len() as u8); alpn.put(proto.as_slice()); } @@ -290,8 +290,7 @@ where let h2 = sock .ssl() .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); + .map_or(false, |protos| protos.windows(2).any(|w| w == H2)); if h2 { (Box::new(sock), Protocol::Http2) } else { @@ -325,8 +324,7 @@ where .get_ref() .1 .get_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); + .map_or(false, |protos| protos.windows(2).any(|w| w == H2)); if h2 { (Box::new(sock), Protocol::Http2) } else { diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index cf423ef12..b9d5f96bd 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -168,14 +168,13 @@ where if let Err(e) = send.send_data(bytes, false) { return Err(e.into()); - } else { - if !b.is_empty() { - send.reserve_capacity(b.len()); - } else { - buf = None; - } - continue; } + if !b.is_empty() { + send.reserve_capacity(b.len()); + } else { + buf = None; + } + continue; } Some(Err(e)) => return Err(e.into()), } diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 9a2293e92..0e01e8748 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -152,8 +152,8 @@ impl ServiceConfig { } } - #[inline] /// Return keep-alive timer delay is configured. + #[inline] pub fn keep_alive_timer(&self) -> Option { self.keep_alive().map(|ka| sleep_until(self.now() + ka)) } @@ -365,11 +365,11 @@ mod tests { let clone3 = service.clone(); drop(clone1); - assert_eq!(false, notify_on_drop::is_dropped()); + assert!(!notify_on_drop::is_dropped()); drop(clone2); - assert_eq!(false, notify_on_drop::is_dropped()); + assert!(!notify_on_drop::is_dropped()); drop(clone3); - assert_eq!(false, notify_on_drop::is_dropped()); + assert!(!notify_on_drop::is_dropped()); drop(service); assert!(notify_on_drop::is_dropped()); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d9e1a1ed2..6c3d692c3 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -125,7 +125,7 @@ impl fmt::Display for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - self.inner.cause.as_ref().map(|err| err.as_ref()) + self.inner.cause.as_ref().map(Box::as_ref) } } diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 8aba9f623..f240710c2 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -102,7 +102,7 @@ pub(crate) trait MessageType: Sized { } // transfer-encoding header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str().map(|s| s.trim()) { + if let Ok(s) = value.to_str().map(str::trim) { chunked = s.eq_ignore_ascii_case("chunked"); } else { return Err(ParseError::Header); @@ -110,7 +110,7 @@ pub(crate) trait MessageType: Sized { } // connection keep-alive state header::CONNECTION => { - ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + ka = if let Ok(conn) = value.to_str().map(str::trim) { if conn.eq_ignore_ascii_case("keep-alive") { Some(ConnectionType::KeepAlive) } else if conn.eq_ignore_ascii_case("close") { @@ -125,7 +125,7 @@ pub(crate) trait MessageType: Sized { }; } header::UPGRADE => { - if let Ok(val) = value.to_str().map(|val| val.trim()) { + if let Ok(val) = value.to_str().map(str::trim) { if val.eq_ignore_ascii_case("websocket") { has_upgrade_websocket = true; } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index b4adde638..deb25763c 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -515,14 +515,13 @@ where cx: &mut Context<'_>, ) -> Result<(), DispatchError> { // Handle `EXPECT: 100-Continue` header + let mut this = self.as_mut().project(); if req.head().expect() { // set dispatcher state so the future is pinned. - let mut this = self.as_mut().project(); 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.service.call(req); this.state.set(State::ServiceCall(task)); }; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index e72493fa2..cc771f28a 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -186,8 +186,7 @@ impl Inner { if self .task .as_ref() - .map(|w| !cx.waker().will_wake(w)) - .unwrap_or(true) + .map_or(true, |w| !cx.waker().will_wake(w)) { self.task = Some(cx.waker().clone()); } @@ -199,8 +198,7 @@ impl Inner { if self .io_task .as_ref() - .map(|w| !cx.waker().will_wake(w)) - .unwrap_or(true) + .map_or(true, |w| !cx.waker().will_wake(w)) { self.io_task = Some(cx.waker().clone()); } diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index be33ec02a..634d9282f 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -249,7 +249,7 @@ impl HeaderMap { /// assert!(map.get("INVALID HEADER NAME").is_none()); /// ``` pub fn get(&self, key: impl AsHeaderName) -> Option<&HeaderValue> { - self.get_value(key).map(|val| val.first()) + self.get_value(key).map(Value::first) } /// Returns a mutable reference to the _first_ value associated a header name. @@ -280,8 +280,8 @@ impl HeaderMap { /// ``` pub fn get_mut(&mut self, key: impl AsHeaderName) -> Option<&mut HeaderValue> { match key.try_as_name(super::as_name::Seal).ok()? { - Cow::Borrowed(name) => self.inner.get_mut(name).map(|v| v.first_mut()), - Cow::Owned(name) => self.inner.get_mut(&name).map(|v| v.first_mut()), + Cow::Borrowed(name) => self.inner.get_mut(name).map(Value::first_mut), + Cow::Owned(name) => self.inner.get_mut(&name).map(Value::first_mut), } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 0a3f3a915..e85d686b7 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -152,15 +152,16 @@ impl RequestHead { /// Connection upgrade status pub fn upgrade(&self) -> bool { - if let Some(hdr) = self.headers().get(header::CONNECTION) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("upgrade") - } else { - false - } - } else { - false - } + self.headers() + .get(header::CONNECTION) + .map(|hdr| { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + }) + .unwrap_or(false) } #[inline] @@ -308,13 +309,11 @@ impl ResponseHead { /// Get custom reason for the response #[inline] pub fn reason(&self) -> &str { - if let Some(reason) = self.reason { - reason - } else { + self.reason.unwrap_or_else(|| { self.status .canonical_reason() .unwrap_or("") - } + }) } #[inline] @@ -356,7 +355,7 @@ pub struct Message { impl Message { /// Get new message from the pool of objects pub fn new() -> Self { - T::with_pool(|p| p.get_message()) + T::with_pool(MessagePool::get_message) } } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index ac0b7cea1..747042527 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, Ident, NestedMeta}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta}; enum ResourceType { Async, @@ -227,8 +227,7 @@ impl Route { format!( r#"invalid service definition, expected #[{}("")]"#, method - .map(|it| it.as_str()) - .unwrap_or("route") + .map_or("route", |it| it.as_str()) .to_ascii_lowercase() ), )); @@ -298,7 +297,7 @@ impl ToTokens for Route { } = self; let resource_name = resource_name .as_ref() - .map_or_else(|| name.to_string(), |n| n.value()); + .map_or_else(|| name.to_string(), LitStr::value); let method_guards = { let mut others = methods.iter(); // unwrapping since length is checked to be at least one diff --git a/awc/src/request.rs b/awc/src/request.rs index 3f312f6e7..812c76318 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -486,7 +486,9 @@ impl ClientRequest { let mut encoding = vec![]; #[cfg(feature = "compress-brotli")] - encoding.push("br"); + { + encoding.push("br"); + } #[cfg(feature = "compress-gzip")] { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 34b71f052..2fe36399c 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -517,7 +517,7 @@ mod tests { "test-origin" ); assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); + assert!(req.server_mode); assert_eq!(req.protocols, Some("v1,v2".to_string())); assert_eq!( req.head.headers.get(header::CONTENT_TYPE).unwrap(), diff --git a/benches/responder.rs b/benches/responder.rs index 0dfc8cd18..5d0b98d5f 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -1,6 +1,5 @@ use std::{future::Future, time::Instant}; -use actix_http::Response; use actix_utils::future::{ready, Ready}; use actix_web::http::StatusCode; use actix_web::test::TestRequest; @@ -24,11 +23,11 @@ struct StringResponder(String); impl FutureResponder for StringResponder { type Error = Error; - type Future = Ready>; + type Future = Ready>; fn future_respond_to(self, _: &HttpRequest) -> Self::Future { // this is default builder for string response in both new and old responder trait. - ready(Ok(Response::build(StatusCode::OK) + ready(Ok(HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self.0))) } @@ -37,7 +36,7 @@ impl FutureResponder for StringResponder { impl FutureResponder for OptionResponder where T: FutureResponder, - T::Future: Future>, + T::Future: Future>, { type Error = Error; type Future = Either>>; @@ -52,7 +51,7 @@ where impl Responder for StringResponder { fn respond_to(self, _: &HttpRequest) -> HttpResponse { - Response::build(StatusCode::OK) + HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self.0) } @@ -62,7 +61,7 @@ impl Responder for OptionResponder { fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self.0 { Some(t) => t.respond_to(req), - None => Response::from_error(error::ErrorInternalServerError("err")), + None => HttpResponse::from_error(error::ErrorInternalServerError("err")), } } } diff --git a/benches/service.rs b/benches/service.rs index 30708477d..87e51f170 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -51,9 +51,8 @@ where fut.await.unwrap(); } }); - let elapsed = start.elapsed(); // check that at least first request succeeded - elapsed + start.elapsed() }) }); } @@ -93,9 +92,8 @@ fn async_web_service(c: &mut Criterion) { fut.await.unwrap(); } }); - let elapsed = start.elapsed(); // check that at least first request succeeded - elapsed + start.elapsed() }) }); } diff --git a/src/app_service.rs b/src/app_service.rs index bdb7ec433..3c1b78474 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -131,9 +131,9 @@ where let service = endpoint_fut.await?; // populate app data container from (async) data factories. - async_data_factories.iter().for_each(|factory| { + for factory in &async_data_factories { factory.create(&mut app_data); - }); + } Ok(AppInitService { service, diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 71c610157..9f67baffb 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -410,41 +410,33 @@ impl ContentDisposition { /// Return the value of *name* if exists. pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).next() + self.parameters.iter().find_map(DispositionParam::as_name) } /// Return the value of *filename* if exists. pub fn get_filename(&self) -> Option<&str> { self.parameters .iter() - .filter_map(|p| p.as_filename()) - .next() + .find_map(DispositionParam::as_filename) } /// Return the value of *filename\** if exists. pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { self.parameters .iter() - .filter_map(|p| p.as_filename_ext()) - .next() + .find_map(DispositionParam::as_filename_ext) } /// Return the value of the parameter which the `name` matches. pub fn get_unknown(&self, name: impl AsRef) -> Option<&str> { let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .next() + self.parameters.iter().find_map(|p| p.as_unknown(name)) } /// Return the value of the extended parameter which the `name` matches. pub fn get_unknown_ext(&self, name: impl AsRef) -> Option<&ExtendedValue> { let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .next() + self.parameters.iter().find_map(|p| p.as_unknown_ext(name)) } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 0eb4d0a83..a9128bc47 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -200,8 +200,7 @@ impl AcceptEncoding { let mut encodings = raw .replace(' ', "") .split(',') - .map(|l| AcceptEncoding::new(l)) - .flatten() + .filter_map(|l| AcceptEncoding::new(l)) .collect::>(); encodings.sort(); diff --git a/src/request.rs b/src/request.rs index 5c5c43d26..36d9aba98 100644 --- a/src/request.rs +++ b/src/request.rs @@ -111,11 +111,7 @@ impl HttpRequest { /// E.g., id=10 #[inline] pub fn query_string(&self) -> &str { - if let Some(query) = self.uri().query().as_ref() { - query - } else { - "" - } + self.uri().query().unwrap_or_default() } /// Get a reference to the Path parameters. diff --git a/src/resource.rs b/src/resource.rs index 9f5cf3cb2..4e609f31a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -465,7 +465,7 @@ impl Service for ResourceService { actix_service::always_ready!(); fn call(&self, mut req: ServiceRequest) -> Self::Future { - for route in self.routes.iter() { + for route in &self.routes { if route.check(&mut req) { return route.call(req); } diff --git a/src/response/builder.rs b/src/response/builder.rs index 6e013cae2..56d30d9d0 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -406,7 +406,7 @@ impl HttpResponseBuilder { return None; } - self.res.as_mut().map(|res| res.head_mut()) + self.res.as_mut().map(Response::head_mut) } } diff --git a/src/server.rs b/src/server.rs index 89328215d..804b3e367 100644 --- a/src/server.rs +++ b/src/server.rs @@ -292,15 +292,15 @@ where let c = cfg.lock().unwrap(); let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - let svc = HttpService::build() + let mut svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) .local_addr(addr); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) - } else { - svc + if let Some(handler) = on_connect_fn.clone() { + svc = svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) }; let fac = factory() @@ -461,17 +461,15 @@ where } } - if !success { - if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) - } - } else { + if success { Ok(sockets) + } else if let Some(e) = err.take() { + Err(e) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "Can not bind to address.", + )) } } @@ -537,15 +535,14 @@ where ); fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ - let svc = HttpService::build() + let mut svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)) - } else { - svc - }; + if let Some(handler) = on_connect_fn.clone() { + svc = svc + .on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)); + } let fac = factory() .into_factory() diff --git a/src/service.rs b/src/service.rs index 592577467..c1bffac49 100644 --- a/src/service.rs +++ b/src/service.rs @@ -167,11 +167,7 @@ impl ServiceRequest { /// E.g., id=10 #[inline] pub fn query_string(&self) -> &str { - if let Some(query) = self.uri().query().as_ref() { - query - } else { - "" - } + self.uri().query().unwrap_or_default() } /// Peer socket address. diff --git a/src/types/form.rs b/src/types/form.rs index 4ce075d99..c81f73554 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -1,6 +1,7 @@ //! For URL encoded form helper documentation, see [`Form`]. use std::{ + borrow::Cow, fmt, future::Future, ops, @@ -384,7 +385,7 @@ where } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) + .map(Cow::into_owned) .ok_or(UrlencodedError::Encoding)?; serde_urlencoded::from_str::(&body).map_err(UrlencodedError::Parse) diff --git a/src/types/path.rs b/src/types/path.rs index 9dab79414..f2273a59b 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -103,8 +103,7 @@ where fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req .app_data::() - .map(|c| c.ehandler.clone()) - .unwrap_or(None); + .and_then(|c| c.ehandler.clone()); ready( de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) diff --git a/src/types/payload.rs b/src/types/payload.rs index 87378701b..188da6201 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -1,6 +1,7 @@ //! Basic binary and string payload extractors. use std::{ + borrow::Cow, future::Future, pin::Pin, str, @@ -190,7 +191,7 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result Self::Future { let error_handler = req .app_data::() - .map(|c| c.err_handler.clone()) - .unwrap_or(None); + .and_then(|c| c.err_handler.clone()); serde_urlencoded::from_str::(req.query_string()) .map(|val| ok(Query(val))) From 604be5495f920a9f11056fad989ff757ed364f2b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Jun 2021 16:33:36 +0100 Subject: [PATCH 038/861] prepare beta.8 releases (#2292) --- .github/ISSUE_TEMPLATE/config.yml | 13 +++---------- CHANGES.md | 4 +++- Cargo.toml | 6 +++--- README.md | 4 ++-- actix-files/CHANGES.md | 10 ++++++---- actix-files/Cargo.toml | 12 +++++------- actix-files/README.md | 7 +++---- actix-http-test/Cargo.toml | 6 +++--- actix-http-test/README.md | 6 ++++-- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 6 ++---- actix-http/README.md | 7 +++---- actix-multipart/Cargo.toml | 4 ++-- actix-multipart/README.md | 2 +- actix-test/Cargo.toml | 6 +++--- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/Cargo.toml | 12 +++++------- actix-web-actors/README.md | 7 +++---- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 3 +-- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 8 ++++---- awc/README.md | 7 +++---- src/lib.rs | 1 - 24 files changed, 69 insertions(+), 73 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d8c6d66ca..bfa124ffd 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,15 +1,8 @@ blank_issues_enabled: true contact_links: - - name: GitHub Discussions - url: https://github.com/actix/actix-web/discussions - about: Actix Web Q&A - - name: Gitter chat (actix-web) - url: https://gitter.im/actix/actix-web - about: Actix Web Q&A - - name: Gitter chat (actix) - url: https://gitter.im/actix/actix - about: Actix (actor framework) Q&A - name: Actix Discord url: https://discord.gg/NWpN5mmg3x about: Actix developer discussion and community chat - + - name: GitHub Discussions + url: https://github.com/actix/actix-web/discussions + about: Actix Web Q&A diff --git a/CHANGES.md b/CHANGES.md index f6a0b28c8..d0f2188a7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.8 - 2021-06-26 ### Added * Add `ServiceRequest::parts_mut`. [#2177] * Add extractors for `Uri` and `Method`. [#2263] @@ -12,7 +15,6 @@ * Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] - ### Fixed * Scope and Resource middleware can access data items set on their own layer. [#2288] diff --git a/Cargo.toml b/Cargo.toml index 293e6bccd..7556bd8d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.7" +version = "4.0.0-beta.8" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -75,7 +75,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.7" +actix-http = "3.0.0-beta.8" ahash = "0.7" bytes = "1" @@ -104,7 +104,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.6", features = ["openssl"] } +awc = { version = "3.0.0-beta.7", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/README.md b/README.md index d9048a06b..309a18466 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web/4.0.0-beta.7) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web/4.0.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.7) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.8)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 54b0344a4..db047c44c 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.6 - 2021-06-26 * Added `Files::path_filter()`. [#2274] +* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 +[#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 @@ -11,22 +16,19 @@ * For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] * `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] * `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] -* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 [#2225]: https://github.com/actix/actix-web/pull/2225 [#2257]: https://github.com/actix/actix-web/pull/2257 -[#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.4 - 2021-04-02 -* No notable changes. - * Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 + ## 0.6.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 65dce628b..ef288215b 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,13 +1,11 @@ [package] name = "actix-files" -version = "0.6.0-beta.5" +version = "0.6.0-beta.6" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" -readme = "README.md" keywords = ["actix", "http", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-files/" +repository = "https://github.com/actix/actix-web" categories = ["asynchronous", "web-programming::http-server"] license = "MIT OR Apache-2.0" edition = "2018" @@ -17,8 +15,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.7", default-features = false } -actix-http = "3.0.0-beta.7" +actix-web = { version = "4.0.0-beta.8", default-features = false } +actix-http = "3.0.0-beta.8" actix-service = "2.0.0" actix-utils = "3.0.0" @@ -35,5 +33,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.7" +actix-web = "4.0.0-beta.8" actix-test = "0.1.0-beta.3" diff --git a/actix-files/README.md b/actix-files/README.md index 524f5c38e..13c301c56 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,17 +3,16 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.5)](https://docs.rs/actix-files/0.6.0-beta.5) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.6)](https://docs.rs/actix-files/0.6.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.5/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.5) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.6/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum supported Rust version: 1.46 or later diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 5d797aaa9..c04b5da49 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.6", default-features = false } +awc = { version = "3.0.0-beta.7", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.7", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.7" +actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.8" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index b8cf450d4..74260a352 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,12 +4,14 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) +
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4) -[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http-test) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.46.0 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 435607463..8ead43718 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.8 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 35ea89862..a12fed4b9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,13 +1,11 @@ [package] name = "actix-http" -version = "3.0.0-beta.7" +version = "3.0.0-beta.8" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" -readme = "README.md" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-http/" +repository = "https://github.com/actix/actix-web" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] diff --git a/actix-http/README.md b/actix-http/README.md index 5271d8738..de1ef0a9b 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,18 +3,17 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.7)](https://docs.rs/actix-http/3.0.0-beta.7) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.8)](https://docs.rs/actix-http/3.0.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.7) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.8) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.46.0 ## Example diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 41b0fbae7..5103407ca 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.7", default-features = false } +actix-web = { version = "4.0.0-beta.8", default-features = false } actix-utils = "3.0.0" bytes = "1" @@ -31,6 +31,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.7" +actix-http = "3.0.0-beta.8" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index f6d008fc3..78855b815 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -9,9 +9,9 @@
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-multipart) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.46.0 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index ca814e0e5..b732cf744 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -20,13 +20,13 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0" -actix-http = "3.0.0-beta.7" +actix-http = "3.0.0-beta.8" actix-http-test = { version = "3.0.0-beta.4", features = [] } actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.7", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.6", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.7", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index decbe2219..bf642ef95 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.6 - 2021-06-26 * Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 669cd4001..fcb5195b8 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,13 +1,11 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.5" +version = "4.0.0-beta.6" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" -readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web-actors/" +repository = "https://github.com/actix/actix-web" license = "MIT OR Apache-2.0" edition = "2018" @@ -18,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.0" -actix-http = "3.0.0-beta.7" -actix-web = { version = "4.0.0-beta.7", default-features = false } +actix-http = "3.0.0-beta.8" +actix-web = { version = "4.0.0-beta.8", default-features = false } bytes = "1" bytestring = "1" @@ -31,6 +29,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.3" -awc = { version = "3.0.0-beta.6", default-features = false } +awc = { version = "3.0.0-beta.7", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 0d926f5ee..5f8f78bde 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,16 +3,15 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web-actors/4.0.0-beta.5) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.6)](https://docs.rs/actix-web-actors/4.0.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.5) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-actors) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum supported Rust version: 1.46 or later diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 327f16bc5..4d0fd5e26 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -22,7 +22,7 @@ proc-macro2 = "1" actix-rt = "2.2" actix-test = "0.1.0-beta.3" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.7" +actix-web = "4.0.0-beta.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index ef3aa72df..96e4cb51f 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -9,12 +9,11 @@
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum supported Rust version: 1.46 or later. ## Compile Testing diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 2e56eb958..16132be1c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.7 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 26c625a05..016d3b48b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.6" +version = "3.0.0-beta.7" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -55,7 +55,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" -actix-http = "3.0.0-beta.7" +actix-http = "3.0.0-beta.8" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -77,8 +77,8 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.7", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.7", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.8", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.8", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" diff --git a/awc/README.md b/awc/README.md index 5076c59a4..dd08c6e10 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,16 +3,15 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.6)](https://docs.rs/awc/3.0.0-beta.6) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.7)](https://docs.rs/awc/3.0.0-beta.7) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.6/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.6) -[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.7/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.7) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) -- [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.46.0 ## Example diff --git a/src/lib.rs b/src/lib.rs index 920abccb6..6905da79f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,6 @@ //! * [Website & User Guide](https://actix.rs/) //! * [Examples Repository](https://github.com/actix/examples) //! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x) -//! * [Community Chat on Gitter](https://gitter.im/actix/actix-web) //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! From 2504c2ecb0906d261688cd61d93444d4f0537cde Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sun, 27 Jun 2021 02:44:56 -0400 Subject: [PATCH 039/861] Move dev module to separate file, update description (#2293) --- src/dev.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 107 +---------------------------------------------------- 2 files changed, 103 insertions(+), 106 deletions(-) create mode 100644 src/dev.rs diff --git a/src/dev.rs b/src/dev.rs new file mode 100644 index 000000000..a656604e3 --- /dev/null +++ b/src/dev.rs @@ -0,0 +1,102 @@ +//! Lower level `actix-web` types. +//! +//! Most users will not have to interact with the types in this module, +//! but it is useful as a glob import for those writing middleware, developing libraries, +//! or interacting with the service API directly: +//! +//! ``` +//! # #![allow(unused_imports)] +//! use actix_web::dev::*; +//! ``` + +pub use crate::config::{AppConfig, AppService}; +#[doc(hidden)] +pub use crate::handler::Handler; +pub use crate::info::{ConnectionInfo, PeerAddr}; +pub use crate::rmap::ResourceMap; +pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; + +pub use crate::types::form::UrlEncoded; +pub use crate::types::json::JsonBody; +pub use crate::types::readlines::Readlines; + +pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream}; + +#[cfg(feature = "__compress")] +pub use actix_http::encoding::Decoder as Decompress; +pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; +pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; +pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; +pub use actix_server::Server; +pub use actix_service::{ + always_ready, fn_factory, fn_service, forward_ready, Service, Transform, +}; + +pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { + for path in &mut patterns { + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + } + patterns +} + +use crate::http::header::ContentEncoding; +use actix_http::{Response, ResponseBuilder}; + +struct Enc(ContentEncoding); + +/// Helper trait that allows to set specific encoding for response. +pub trait BodyEncoding { + /// Get content encoding + fn get_encoding(&self) -> Option; + + /// Set content encoding + /// + /// Must be used with [`crate::middleware::Compress`] to take effect. + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; +} + +impl BodyEncoding for ResponseBuilder { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} + +impl BodyEncoding for Response { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} + +impl BodyEncoding for crate::HttpResponseBuilder { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} + +impl BodyEncoding for crate::HttpResponse { + fn get_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} diff --git a/src/lib.rs b/src/lib.rs index 6905da79f..714c759cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ mod app; mod app_service; mod config; mod data; +pub mod dev; pub mod error; mod extract; pub mod guard; @@ -115,109 +116,3 @@ pub use crate::scope::Scope; pub use crate::server::HttpServer; // TODO: is exposing the error directly really needed pub use crate::types::{Either, EitherExtractError}; - -pub mod dev { - //! The `actix-web` prelude for library developers - //! - //! The purpose of this module is to alleviate imports of many common actix - //! traits by adding a glob import to the top of actix heavy modules: - //! - //! ``` - //! # #![allow(unused_imports)] - //! use actix_web::dev::*; - //! ``` - - pub use crate::config::{AppConfig, AppService}; - #[doc(hidden)] - pub use crate::handler::Handler; - pub use crate::info::{ConnectionInfo, PeerAddr}; - pub use crate::rmap::ResourceMap; - pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; - - pub use crate::types::form::UrlEncoded; - pub use crate::types::json::JsonBody; - pub use crate::types::readlines::Readlines; - - pub use actix_http::body::{ - AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream, - }; - - #[cfg(feature = "__compress")] - pub use actix_http::encoding::Decoder as Decompress; - pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; - pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; - pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; - pub use actix_server::Server; - pub use actix_service::{ - always_ready, fn_factory, fn_service, forward_ready, Service, Transform, - }; - - pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { - for path in &mut patterns { - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - } - patterns - } - - use crate::http::header::ContentEncoding; - use actix_http::{Response, ResponseBuilder}; - - struct Enc(ContentEncoding); - - /// Helper trait that allows to set specific encoding for response. - pub trait BodyEncoding { - /// Get content encoding - fn get_encoding(&self) -> Option; - - /// Set content encoding - /// - /// Must be used with [`crate::middleware::Compress`] to take effect. - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; - } - - impl BodyEncoding for ResponseBuilder { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } - - impl BodyEncoding for Response { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } - - impl BodyEncoding for crate::HttpResponseBuilder { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } - - impl BodyEncoding for crate::HttpResponse { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } -} From d8deed0475a5801d531e72b7e2619e18222f704f Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sat, 10 Jul 2021 01:57:21 +0300 Subject: [PATCH 040/861] fix tests with tokio 1.8.1 (#2317) --- actix-http/src/config.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 0e01e8748..6661e18f3 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -326,7 +326,7 @@ mod notify_on_drop { mod tests { use super::*; - use actix_rt::task::yield_now; + use actix_rt::{task::yield_now, time::sleep}; #[actix_rt::test] async fn test_date_service_update() { @@ -350,7 +350,14 @@ mod tests { assert_ne!(buf1, buf2); drop(settings); - assert!(notify_on_drop::is_dropped()); + + // Ensure the task will drop eventually + let mut times = 0; + while !notify_on_drop::is_dropped() { + sleep(Duration::from_millis(100)).await; + times += 1; + assert!(times < 10, "Timeout waiting for task drop"); + } } #[actix_rt::test] @@ -372,7 +379,14 @@ mod tests { assert!(!notify_on_drop::is_dropped()); drop(service); - assert!(notify_on_drop::is_dropped()); + + // Ensure the task will drop eventually + let mut times = 0; + while !notify_on_drop::is_dropped() { + sleep(Duration::from_millis(100)).await; + times += 1; + assert!(times < 10, "Timeout waiting for task drop"); + } } #[test] From 7ae132cb688c213b22878b8425e73b9431300629 Mon Sep 17 00:00:00 2001 From: CGMossa Date: Mon, 12 Jul 2021 03:02:19 +0200 Subject: [PATCH 041/861] Update MIGRATION.md (#2315) Minor edit --- MIGRATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9c29b8db9..785974366 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -5,8 +5,8 @@ routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. - Before: `#[get("/test/")` - After: `#[get("/test")` + Before: `#[get("/test/")]` + After: `#[get("/test")]` Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. From 5a14ffeef28859d387810151d5a685b585f36be2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Jul 2021 16:55:24 +0100 Subject: [PATCH 042/861] clippy fixes (#2296) --- .cargo/config.toml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- actix-http/src/config.rs | 2 ++ actix-http/src/error.rs | 2 ++ src/info.rs | 10 +++++----- src/server.rs | 16 +++++++++++----- tests/test_error_propagation.rs | 2 +- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 72f445d8a..db47ca46d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [alias] chk = "check --workspace --all-features --tests --examples --bins" -lint = "clippy --workspace --tests --examples" +lint = "clippy --workspace --all-features --tests --examples --bins" ci-min = "hack check --workspace --no-default-features" ci-min-test = "hack check --workspace --no-default-features --tests --examples" ci-default = "check --workspace --bins --tests --examples" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 42deadf5a..d617cf708 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ PR_TYPE ## PR Checklist - - [ ] Tests for the changes have been added / updated. diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 6661e18f3..97750ff76 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -104,6 +104,8 @@ impl ServiceConfig { } /// Returns the local address that this server is bound to. + /// + /// Returns `None` for connections via UDS (Unix Domain Socket). #[inline] pub fn local_addr(&self) -> Option { self.0.local_addr diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 6c3d692c3..54666e072 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -55,6 +55,8 @@ impl Error { Self::new(Kind::Io) } + // used in encoder behind feature flag so ignore unused warning + #[allow(unused)] pub(crate) fn new_encoder() -> Self { Self::new(Kind::Encoder) } diff --git a/src/info.rs b/src/info.rs index 1f8263add..de8ad67ee 100644 --- a/src/info.rs +++ b/src/info.rs @@ -65,10 +65,10 @@ fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option< /// [rfc7239-63]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.3 #[derive(Debug, Clone, Default)] pub struct ConnectionInfo { - scheme: String, host: String, - realip_remote_addr: Option, + scheme: String, remote_addr: Option, + realip_remote_addr: Option, } impl ConnectionInfo { @@ -135,7 +135,7 @@ impl ConnectionInfo { .or_else(|| first_header_value(req, &*X_FORWARDED_HOST)) .or_else(|| req.headers.get(&header::HOST)?.to_str().ok()) .or_else(|| req.uri.authority().map(Authority::as_str)) - .unwrap_or(cfg.host()) + .unwrap_or_else(|| cfg.host()) .to_owned(); let realip_remote_addr = realip_remote_addr @@ -145,9 +145,9 @@ impl ConnectionInfo { let remote_addr = req.peer_addr.map(|addr| addr.to_string()); ConnectionInfo { - remote_addr, - scheme, host, + scheme, + remote_addr, realip_remote_addr, } } diff --git a/src/server.rs b/src/server.rs index 804b3e367..f15183f85 100644 --- a/src/server.rs +++ b/src/server.rs @@ -295,6 +295,7 @@ where let mut svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown) .local_addr(addr); if let Some(handler) = on_connect_fn.clone() { @@ -352,7 +353,8 @@ where let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + .client_disconnect(c.client_shutdown) + .local_addr(addr); let svc = if let Some(handler) = on_connect_fn.clone() { svc.on_connect_ext(move |io: &_, ext: _| { @@ -523,10 +525,11 @@ where addr: socket_addr, }); - let addr = format!("actix-web-service-{:?}", lst.local_addr()?); + let addr = lst.local_addr()?; + let name = format!("actix-web-service-{:?}", addr); let on_connect_fn = self.on_connect_fn.clone(); - self.builder = self.builder.listen_uds(addr, lst, move || { + self.builder = self.builder.listen_uds(name, lst, move || { let c = cfg.lock().unwrap(); let config = AppConfig::new( false, @@ -537,7 +540,8 @@ where fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ let mut svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown); if let Some(handler) = on_connect_fn.clone() { svc = svc @@ -554,8 +558,8 @@ where Ok(self) } - #[cfg(unix)] /// Start listening for incoming unix domain connections. + #[cfg(unix)] pub fn bind_uds(mut self, addr: A) -> io::Result where A: AsRef, @@ -568,6 +572,7 @@ where let factory = self.factory.clone(); let socket_addr = net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); + self.sockets.push(Socket { scheme: "http", addr: socket_addr, @@ -592,6 +597,7 @@ where HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown) .finish(map_config(fac, move |_| config.clone())), ) }, diff --git a/tests/test_error_propagation.rs b/tests/test_error_propagation.rs index 3e7320920..958276b62 100644 --- a/tests/test_error_propagation.rs +++ b/tests/test_error_propagation.rs @@ -23,7 +23,7 @@ impl std::fmt::Display for MyError { #[get("/test")] async fn test() -> Result { - return Err(MyError.into()); + Err(MyError.into()) } #[derive(Clone)] From 293c52c3ef42663d90d4b09acd22869ee6919788 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Jul 2021 16:55:41 +0100 Subject: [PATCH 043/861] re-export ServiceFactory (#2325) --- CHANGES.md | 4 ++++ actix-http/src/request.rs | 2 +- src/dev.rs | 25 +++++++++---------------- src/request.rs | 4 ++-- src/resource.rs | 4 ++-- src/response/response.rs | 16 ++++++++-------- src/service.rs | 9 +++++---- 7 files changed, 31 insertions(+), 33 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d0f2188a7..88295ec12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Re-export actix-service `ServiceFactory` in `dev` module. [#2325] + +[#2325]: https://github.com/actix/actix-web/pull/2325 ## 4.0.0-beta.8 - 2021-06-26 diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 09c6dd296..401e9745c 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -15,7 +15,7 @@ use crate::{ HttpMessage, }; -/// Request +/// An HTTP request. pub struct Request

{ pub(crate) payload: Payload

, pub(crate) head: Message, diff --git a/src/dev.rs b/src/dev.rs index a656604e3..b8d95efbb 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -1,13 +1,7 @@ -//! Lower level `actix-web` types. +//! Lower-level types and re-exports. //! -//! Most users will not have to interact with the types in this module, -//! but it is useful as a glob import for those writing middleware, developing libraries, -//! or interacting with the service API directly: -//! -//! ``` -//! # #![allow(unused_imports)] -//! use actix_web::dev::*; -//! ``` +//! Most users will not have to interact with the types in this module, but it is useful for those +//! writing extractors, middleware and libraries, or interacting with the service API directly. pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] @@ -24,26 +18,25 @@ pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, S #[cfg(feature = "__compress")] pub use actix_http::encoding::Decoder as Decompress; -pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; pub use actix_service::{ - always_ready, fn_factory, fn_service, forward_ready, Service, Transform, + always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, }; -pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { +use crate::http::header::ContentEncoding; +use actix_http::{Response, ResponseBuilder}; + +pub(crate) fn insert_leading_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/'); }; } + patterns } - -use crate::http::header::ContentEncoding; -use actix_http::{Response, ResponseBuilder}; - struct Enc(ContentEncoding); /// Helper trait that allows to set specific encoding for response. diff --git a/src/request.rs b/src/request.rs index 36d9aba98..4b950e758 100644 --- a/src/request.rs +++ b/src/request.rs @@ -23,10 +23,10 @@ use crate::{ #[cfg(feature = "cookies")] struct Cookies(Vec>); +/// An incoming request. #[derive(Clone)] -/// An HTTP Request pub struct HttpRequest { - /// # Panics + /// # Invariant /// `Rc` is used exclusively and NO `Weak` /// is allowed anywhere in the code. Weak pointer is purposely ignored when /// doing `Rc`'s ref counter check. Expect panics if this invariant is violated. diff --git a/src/resource.rs b/src/resource.rs index 4e609f31a..20d1ee17e 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -15,7 +15,7 @@ use futures_util::future::join_all; use crate::{ data::Data, - dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}, + dev::{insert_leading_slash, AppService, HttpServiceFactory, ResourceDef}, guard::Guard, handler::Handler, responder::Responder, @@ -391,7 +391,7 @@ where }; let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_slash(self.rdef.clone())) + ResourceDef::new(insert_leading_slash(self.rdef.clone())) } else { ResourceDef::new(self.rdef.clone()) }; diff --git a/src/response/response.rs b/src/response/response.rs index 9a3bb2874..09515c839 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -24,20 +24,14 @@ use { use crate::{error::Error, HttpResponseBuilder}; -/// An HTTP Response +/// An outgoing response. pub struct HttpResponse { res: Response, pub(crate) error: Option, } impl HttpResponse { - /// Create HTTP response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponseBuilder::new(status) - } - - /// Create a response. + /// Constructs a response. #[inline] pub fn new(status: StatusCode) -> Self { Self { @@ -46,6 +40,12 @@ impl HttpResponse { } } + /// Constructs a response builder with specific HTTP status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + HttpResponseBuilder::new(status) + } + /// Create an error response. #[inline] pub fn from_error(error: impl Into) -> Self { diff --git a/src/service.rs b/src/service.rs index c1bffac49..47e7e4acc 100644 --- a/src/service.rs +++ b/src/service.rs @@ -14,7 +14,7 @@ use cookie::{Cookie, ParseError as CookieParseError}; use crate::{ config::{AppConfig, AppService}, - dev::insert_slash, + dev::insert_leading_slash, guard::Guard, info::ConnectionInfo, rmap::ResourceMap, @@ -59,9 +59,9 @@ where } } -/// An service http request +/// A service level request wrapper. /// -/// ServiceRequest allows mutable access to request's internal structures +/// Allows mutable access to request's internal structures. pub struct ServiceRequest { req: HttpRequest, payload: Payload, @@ -325,6 +325,7 @@ impl fmt::Debug for ServiceRequest { } } +/// A service level response wrapper. pub struct ServiceResponse { request: HttpRequest, response: HttpResponse, @@ -550,7 +551,7 @@ where }; let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_slash(self.rdef)) + ResourceDef::new(insert_leading_slash(self.rdef)) } else { ResourceDef::new(self.rdef) }; From f6e69919ede4872c1d987b4b932c44580190971c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 6 Aug 2021 22:42:31 +0100 Subject: [PATCH 044/861] update to router 0.5.0 beta (#2339) --- Cargo.toml | 4 +- actix-router/CHANGES.md | 120 ++ actix-router/Cargo.toml | 38 + actix-router/LICENSE-APACHE | 1 + actix-router/LICENSE-MIT | 1 + actix-router/benches/router.rs | 194 +++ actix-router/examples/flamegraph.rs | 169 +++ actix-router/src/de.rs | 723 +++++++++++ actix-router/src/lib.rs | 149 +++ actix-router/src/path.rs | 220 ++++ actix-router/src/resource.rs | 1803 +++++++++++++++++++++++++++ actix-router/src/router.rs | 281 +++++ actix-router/src/url.rs | 288 +++++ src/app.rs | 2 +- src/app_service.rs | 2 +- src/config.rs | 2 +- src/dev.rs | 21 +- src/request.rs | 8 +- src/resource.rs | 12 +- src/rmap.rs | 44 +- src/scope.rs | 2 +- src/service.rs | 20 +- src/types/path.rs | 8 +- src/web.rs | 8 +- 24 files changed, 4063 insertions(+), 57 deletions(-) create mode 100644 actix-router/CHANGES.md create mode 100644 actix-router/Cargo.toml create mode 120000 actix-router/LICENSE-APACHE create mode 120000 actix-router/LICENSE-MIT create mode 100644 actix-router/benches/router.rs create mode 100644 actix-router/examples/flamegraph.rs create mode 100644 actix-router/src/de.rs create mode 100644 actix-router/src/lib.rs create mode 100644 actix-router/src/path.rs create mode 100644 actix-router/src/resource.rs create mode 100644 actix-router/src/router.rs create mode 100644 actix-router/src/url.rs diff --git a/Cargo.toml b/Cargo.toml index 7556bd8d7..ff3321f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "actix-web-codegen", "actix-http-test", "actix-test", + "actix-router", ] # enable when MSRV is 1.51+ # resolver = "2" @@ -67,7 +68,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-macros = "0.2.1" -actix-router = "0.2.7" +actix-router = "0.5.0-beta.1" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" @@ -126,6 +127,7 @@ actix-files = { path = "actix-files" } actix-http = { path = "actix-http" } actix-http-test = { path = "actix-http-test" } actix-multipart = { path = "actix-multipart" } +actix-router = { path = "actix-router" } actix-test = { path = "actix-test" } actix-web = { path = "." } actix-web-actors = { path = "actix-web-actors" } diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md new file mode 100644 index 000000000..dea7cb76f --- /dev/null +++ b/actix-router/CHANGES.md @@ -0,0 +1,120 @@ +# Changes + +## Unreleased - 2021-xx-xx +* Introduce `ResourceDef::join`. [#380] +* Disallow prefix routes with tail segments. [#379] +* Enforce path separators on dynamic prefixes. [#378] +* Improve malformed path error message. [#384] + +[#378]: https://github.com/actix/actix-net/pull/378 +[#379]: https://github.com/actix/actix-net/pull/379 +[#380]: https://github.com/actix/actix-net/pull/380 +[#384]: https://github.com/actix/actix-net/pull/384 + + +## 0.5.0-beta.1 - 2021-07-20 +* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +* Fix `ResourceDef` `PartialEq` implementation. [#373] +* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +* Rename `Router::{*_checked => *_fn}`. [#373] +* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] + +[#368]: https://github.com/actix/actix-net/pull/368 +[#366]: https://github.com/actix/actix-net/pull/366 +[#368]: https://github.com/actix/actix-net/pull/368 +[#370]: https://github.com/actix/actix-net/pull/370 +[#371]: https://github.com/actix/actix-net/pull/371 +[#372]: https://github.com/actix/actix-net/pull/372 +[#373]: https://github.com/actix/actix-net/pull/373 + + +## 0.4.0 - 2021-06-06 +* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +* Path tail patterns now match new lines (`\n`) in request URL. [#360] +* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] + +[#345]: https://github.com/actix/actix-net/pull/345 +[#357]: https://github.com/actix/actix-net/pull/357 +[#359]: https://github.com/actix/actix-net/pull/359 +[#360]: https://github.com/actix/actix-net/pull/360 + + +## 0.3.0 - 2019-12-31 +* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 + + +## 0.2.7 - 2021-02-06 +* Add `Router::recognize_checked` [#247] + +[#247]: https://github.com/actix/actix-net/pull/247 + + +## 0.2.6 - 2021-01-09 +* Use `bytestring` version range compatible with Bytes v1.0. [#246] + +[#246]: https://github.com/actix/actix-net/pull/246 + + +## 0.2.5 - 2020-09-20 +* Fix `from_hex()` method + + +## 0.2.4 - 2019-12-31 +* Add `ResourceDef::resource_path_named()` path generation method + + +## 0.2.3 - 2019-12-25 +* Add impl `IntoPattern` for `&String` + + +## 0.2.2 - 2019-12-25 +* Use `IntoPattern` for `RouterBuilder::path()` + + +## 0.2.1 - 2019-12-25 +* Add `IntoPattern` trait +* Add multi-pattern resources + + +## 0.2.0 - 2019-12-07 +* Update http to 0.2 +* Update regex to 1.3 +* Use bytestring instead of string + + +## 0.1.5 - 2019-05-15 +* Remove debug prints + + +## 0.1.4 - 2019-05-15 +* Fix checked resource match + + +## 0.1.3 - 2019-04-22 +* Added support for `remainder match` (i.e "/path/{tail}*") + + +## 0.1.2 - 2019-04-07 +* Export `Quoter` type +* Allow to reset `Path` instance + + +## 0.1.1 - 2019-04-03 +* Get dynamic segment by name instead of iterator. + + +## 0.1.0 - 2019-03-09 +* Initial release diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml new file mode 100644 index 000000000..2a2ce1cc1 --- /dev/null +++ b/actix-router/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "actix-router" +version = "0.5.0-beta.1" +authors = [ + "Nikolay Kim ", + "Ali MJ Al-Nasrawy ", + "Rob Ede ", +] +description = "Resource path matching and router" +keywords = ["actix", "router", "routing"] +repository = "https://github.com/actix/actix-net.git" +license = "MIT OR Apache-2.0" +edition = "2018" + +[lib] +name = "actix_router" +path = "src/lib.rs" + +[features] +default = ["http"] + +[dependencies] +bytestring = ">=0.1.5, <2" +firestorm = "0.4" +http = { version = "0.2.3", optional = true } +log = "0.4" +regex = "1.5" +serde = "1" + +[dev-dependencies] +criterion = { version = "0.3", features = ["html_reports"] } +firestorm = { version = "0.4", features = ["enable_system_time"] } +http = "0.2.3" +serde = { version = "1", features = ["derive"] } + +[[bench]] +name = "router" +harness = false diff --git a/actix-router/LICENSE-APACHE b/actix-router/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-router/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-router/LICENSE-MIT b/actix-router/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-router/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-router/benches/router.rs b/actix-router/benches/router.rs new file mode 100644 index 000000000..a428b9f13 --- /dev/null +++ b/actix-router/benches/router.rs @@ -0,0 +1,194 @@ +//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +macro_rules! register { + (colon) => {{ + register!(finish => ":p1", ":p2", ":p3", ":p4") + }}; + (brackets) => {{ + register!(finish => "{p1}", "{p2}", "{p3}", "{p4}") + }}; + (regex) => {{ + register!(finish => "(.*)", "(.*)", "(.*)", "(.*)") + }}; + (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{ + let arr = [ + concat!("/authorizations"), + concat!("/authorizations/", $p1), + concat!("/applications/", $p1, "/tokens/", $p2), + concat!("/events"), + concat!("/repos/", $p1, "/", $p2, "/events"), + concat!("/networks/", $p1, "/", $p2, "/events"), + concat!("/orgs/", $p1, "/events"), + concat!("/users/", $p1, "/received_events"), + concat!("/users/", $p1, "/received_events/public"), + concat!("/users/", $p1, "/events"), + concat!("/users/", $p1, "/events/public"), + concat!("/users/", $p1, "/events/orgs/", $p2), + concat!("/feeds"), + concat!("/notifications"), + concat!("/repos/", $p1, "/", $p2, "/notifications"), + concat!("/notifications/threads/", $p1), + concat!("/notifications/threads/", $p1, "/subscription"), + concat!("/repos/", $p1, "/", $p2, "/stargazers"), + concat!("/users/", $p1, "/starred"), + concat!("/user/starred"), + concat!("/user/starred/", $p1, "/", $p2), + concat!("/repos/", $p1, "/", $p2, "/subscribers"), + concat!("/users/", $p1, "/subscriptions"), + concat!("/user/subscriptions"), + concat!("/repos/", $p1, "/", $p2, "/subscription"), + concat!("/user/subscriptions/", $p1, "/", $p2), + concat!("/users/", $p1, "/gists"), + concat!("/gists"), + concat!("/gists/", $p1), + concat!("/gists/", $p1, "/star"), + concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/refs"), + concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3), + concat!("/issues"), + concat!("/user/issues"), + concat!("/orgs/", $p1, "/issues"), + concat!("/repos/", $p1, "/", $p2, "/issues"), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3), + concat!("/repos/", $p1, "/", $p2, "/assignees"), + concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"), + concat!("/repos/", $p1, "/", $p2, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/labels/", $p3), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/milestones/"), + concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3), + concat!("/emojis"), + concat!("/gitignore/templates"), + concat!("/gitignore/templates/", $p1), + concat!("/meta"), + concat!("/rate_limit"), + concat!("/users/", $p1, "/orgs"), + concat!("/user/orgs"), + concat!("/orgs/", $p1), + concat!("/orgs/", $p1, "/members"), + concat!("/orgs/", $p1, "/members", $p2), + concat!("/orgs/", $p1, "/public_members"), + concat!("/orgs/", $p1, "/public_members/", $p2), + concat!("/orgs/", $p1, "/teams"), + concat!("/teams/", $p1), + concat!("/teams/", $p1, "/members"), + concat!("/teams/", $p1, "/members", $p2), + concat!("/teams/", $p1, "/repos"), + concat!("/teams/", $p1, "/repos/", $p2, "/", $p3), + concat!("/user/teams"), + concat!("/repos/", $p1, "/", $p2, "/pulls"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"), + concat!("/user/repos"), + concat!("/users/", $p1, "/repos"), + concat!("/orgs/", $p1, "/repos"), + concat!("/repositories"), + concat!("/repos/", $p1, "/", $p2), + concat!("/repos/", $p1, "/", $p2, "/contributors"), + concat!("/repos/", $p1, "/", $p2, "/languages"), + concat!("/repos/", $p1, "/", $p2, "/teams"), + concat!("/repos/", $p1, "/", $p2, "/tags"), + concat!("/repos/", $p1, "/", $p2, "/branches"), + concat!("/repos/", $p1, "/", $p2, "/branches/", $p3), + concat!("/repos/", $p1, "/", $p2, "/collaborators"), + concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3), + concat!("/repos/", $p1, "/", $p2, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/commits"), + concat!("/repos/", $p1, "/", $p2, "/commits/", $p3), + concat!("/repos/", $p1, "/", $p2, "/readme"), + concat!("/repos/", $p1, "/", $p2, "/keys"), + concat!("/repos/", $p1, "/", $p2, "/keys", $p3), + concat!("/repos/", $p1, "/", $p2, "/downloads"), + concat!("/repos/", $p1, "/", $p2, "/downloads", $p3), + concat!("/repos/", $p1, "/", $p2, "/forks"), + concat!("/repos/", $p1, "/", $p2, "/hooks"), + concat!("/repos/", $p1, "/", $p2, "/hooks", $p3), + concat!("/repos/", $p1, "/", $p2, "/releases"), + concat!("/repos/", $p1, "/", $p2, "/releases/", $p3), + concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"), + concat!("/repos/", $p1, "/", $p2, "/stats/contributors"), + concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"), + concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"), + concat!("/repos/", $p1, "/", $p2, "/stats/participation"), + concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"), + concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3), + concat!("/search/repositories"), + concat!("/search/code"), + concat!("/search/issues"), + concat!("/search/users"), + concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4), + concat!("/legacy/repos/search/", $p1), + concat!("/legacy/user/search/", $p1), + concat!("/legacy/user/email/", $p1), + concat!("/users/", $p1), + concat!("/user"), + concat!("/users"), + concat!("/user/emails"), + concat!("/users/", $p1, "/followers"), + concat!("/user/followers"), + concat!("/users/", $p1, "/following"), + concat!("/user/following"), + concat!("/user/following/", $p1), + concat!("/users/", $p1, "/following", $p2), + concat!("/users/", $p1, "/keys"), + concat!("/user/keys"), + concat!("/user/keys/", $p1), + ]; + std::array::IntoIter::new(arr) + }}; +} + +fn call() -> impl Iterator { + let arr = [ + "/authorizations", + "/user/repos", + "/repos/rust-lang/rust/stargazers", + "/orgs/rust-lang/public_members/nikomatsakis", + "/repos/rust-lang/rust/releases/1.51.0", + ]; + + std::array::IntoIter::new(arr) +} + +fn compare_routers(c: &mut Criterion) { + let mut group = c.benchmark_group("Compare Routers"); + + let mut actix = actix_router::Router::::build(); + for route in register!(brackets) { + actix.path(route, true); + } + let actix = actix.finish(); + group.bench_function("actix", |b| { + b.iter(|| { + for route in call() { + let mut path = actix_router::Path::new(route); + black_box(actix.recognize(&mut path).unwrap()); + } + }); + }); + + let regex_set = regex::RegexSet::new(register!(regex)).unwrap(); + group.bench_function("regex", |b| { + b.iter(|| { + for route in call() { + black_box(regex_set.matches(route)); + } + }); + }); + + group.finish(); +} + +criterion_group!(benches, compare_routers); +criterion_main!(benches); diff --git a/actix-router/examples/flamegraph.rs b/actix-router/examples/flamegraph.rs new file mode 100644 index 000000000..798cc22d9 --- /dev/null +++ b/actix-router/examples/flamegraph.rs @@ -0,0 +1,169 @@ +macro_rules! register { + (brackets) => {{ + register!(finish => "{p1}", "{p2}", "{p3}", "{p4}") + }}; + (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{ + let arr = [ + concat!("/authorizations"), + concat!("/authorizations/", $p1), + concat!("/applications/", $p1, "/tokens/", $p2), + concat!("/events"), + concat!("/repos/", $p1, "/", $p2, "/events"), + concat!("/networks/", $p1, "/", $p2, "/events"), + concat!("/orgs/", $p1, "/events"), + concat!("/users/", $p1, "/received_events"), + concat!("/users/", $p1, "/received_events/public"), + concat!("/users/", $p1, "/events"), + concat!("/users/", $p1, "/events/public"), + concat!("/users/", $p1, "/events/orgs/", $p2), + concat!("/feeds"), + concat!("/notifications"), + concat!("/repos/", $p1, "/", $p2, "/notifications"), + concat!("/notifications/threads/", $p1), + concat!("/notifications/threads/", $p1, "/subscription"), + concat!("/repos/", $p1, "/", $p2, "/stargazers"), + concat!("/users/", $p1, "/starred"), + concat!("/user/starred"), + concat!("/user/starred/", $p1, "/", $p2), + concat!("/repos/", $p1, "/", $p2, "/subscribers"), + concat!("/users/", $p1, "/subscriptions"), + concat!("/user/subscriptions"), + concat!("/repos/", $p1, "/", $p2, "/subscription"), + concat!("/user/subscriptions/", $p1, "/", $p2), + concat!("/users/", $p1, "/gists"), + concat!("/gists"), + concat!("/gists/", $p1), + concat!("/gists/", $p1, "/star"), + concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/refs"), + concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3), + concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3), + concat!("/issues"), + concat!("/user/issues"), + concat!("/orgs/", $p1, "/issues"), + concat!("/repos/", $p1, "/", $p2, "/issues"), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3), + concat!("/repos/", $p1, "/", $p2, "/assignees"), + concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"), + concat!("/repos/", $p1, "/", $p2, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/labels/", $p3), + concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"), + concat!("/repos/", $p1, "/", $p2, "/milestones/"), + concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3), + concat!("/emojis"), + concat!("/gitignore/templates"), + concat!("/gitignore/templates/", $p1), + concat!("/meta"), + concat!("/rate_limit"), + concat!("/users/", $p1, "/orgs"), + concat!("/user/orgs"), + concat!("/orgs/", $p1), + concat!("/orgs/", $p1, "/members"), + concat!("/orgs/", $p1, "/members", $p2), + concat!("/orgs/", $p1, "/public_members"), + concat!("/orgs/", $p1, "/public_members/", $p2), + concat!("/orgs/", $p1, "/teams"), + concat!("/teams/", $p1), + concat!("/teams/", $p1, "/members"), + concat!("/teams/", $p1, "/members", $p2), + concat!("/teams/", $p1, "/repos"), + concat!("/teams/", $p1, "/repos/", $p2, "/", $p3), + concat!("/user/teams"), + concat!("/repos/", $p1, "/", $p2, "/pulls"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"), + concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"), + concat!("/user/repos"), + concat!("/users/", $p1, "/repos"), + concat!("/orgs/", $p1, "/repos"), + concat!("/repositories"), + concat!("/repos/", $p1, "/", $p2), + concat!("/repos/", $p1, "/", $p2, "/contributors"), + concat!("/repos/", $p1, "/", $p2, "/languages"), + concat!("/repos/", $p1, "/", $p2, "/teams"), + concat!("/repos/", $p1, "/", $p2, "/tags"), + concat!("/repos/", $p1, "/", $p2, "/branches"), + concat!("/repos/", $p1, "/", $p2, "/branches/", $p3), + concat!("/repos/", $p1, "/", $p2, "/collaborators"), + concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3), + concat!("/repos/", $p1, "/", $p2, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"), + concat!("/repos/", $p1, "/", $p2, "/commits"), + concat!("/repos/", $p1, "/", $p2, "/commits/", $p3), + concat!("/repos/", $p1, "/", $p2, "/readme"), + concat!("/repos/", $p1, "/", $p2, "/keys"), + concat!("/repos/", $p1, "/", $p2, "/keys", $p3), + concat!("/repos/", $p1, "/", $p2, "/downloads"), + concat!("/repos/", $p1, "/", $p2, "/downloads", $p3), + concat!("/repos/", $p1, "/", $p2, "/forks"), + concat!("/repos/", $p1, "/", $p2, "/hooks"), + concat!("/repos/", $p1, "/", $p2, "/hooks", $p3), + concat!("/repos/", $p1, "/", $p2, "/releases"), + concat!("/repos/", $p1, "/", $p2, "/releases/", $p3), + concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"), + concat!("/repos/", $p1, "/", $p2, "/stats/contributors"), + concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"), + concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"), + concat!("/repos/", $p1, "/", $p2, "/stats/participation"), + concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"), + concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3), + concat!("/search/repositories"), + concat!("/search/code"), + concat!("/search/issues"), + concat!("/search/users"), + concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4), + concat!("/legacy/repos/search/", $p1), + concat!("/legacy/user/search/", $p1), + concat!("/legacy/user/email/", $p1), + concat!("/users/", $p1), + concat!("/user"), + concat!("/users"), + concat!("/user/emails"), + concat!("/users/", $p1, "/followers"), + concat!("/user/followers"), + concat!("/users/", $p1, "/following"), + concat!("/user/following"), + concat!("/user/following/", $p1), + concat!("/users/", $p1, "/following", $p2), + concat!("/users/", $p1, "/keys"), + concat!("/user/keys"), + concat!("/user/keys/", $p1), + ]; + + arr.to_vec() + }}; +} + +static PATHS: [&str; 5] = [ + "/authorizations", + "/user/repos", + "/repos/rust-lang/rust/stargazers", + "/orgs/rust-lang/public_members/nikomatsakis", + "/repos/rust-lang/rust/releases/1.51.0", +]; + +fn main() { + let mut router = actix_router::Router::::build(); + + for route in register!(brackets) { + router.path(route, true); + } + + let actix = router.finish(); + + if firestorm::enabled() { + firestorm::bench("target", || { + for &route in &PATHS { + let mut path = actix_router::Path::new(route); + actix.recognize(&mut path).unwrap(); + } + }) + .unwrap(); + } +} diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs new file mode 100644 index 000000000..775c48b8a --- /dev/null +++ b/actix-router/src/de.rs @@ -0,0 +1,723 @@ +use serde::de::{self, Deserializer, Error as DeError, Visitor}; +use serde::forward_to_deserialize_any; + +use crate::path::{Path, PathIter}; +use crate::ResourcePath; + +macro_rules! unsupported_type { + ($trait_fn:ident, $name:expr) => { + fn $trait_fn(self, _: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom(concat!( + "unsupported type: ", + $name + ))) + } + }; +} + +macro_rules! parse_single_value { + ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.path.segment_count() != 1 { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected 1", + self.path.segment_count() + ) + .as_str(), + )) + } else { + let v = self.path[0].parse().map_err(|_| { + de::value::Error::custom(format!( + "can not parse {:?} to a {}", + &self.path[0], $tp + )) + })?; + visitor.$visit_fn(v) + } + } + }; +} + +pub struct PathDeserializer<'de, T: ResourcePath> { + path: &'de Path, +} + +impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> { + pub fn new(path: &'de Path) -> Self { + PathDeserializer { path } + } +} + +impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> { + type Error = de::value::Error; + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_map(ParamsDeserializer { + params: self.path.iter(), + current: None, + }) + } + + fn deserialize_struct( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.path.segment_count() < len { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected {}", + self.path.segment_count(), + len + ) + .as_str(), + )) + } else { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + } + + fn deserialize_tuple_struct( + self, + _: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.path.segment_count() < len { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected {}", + self.path.segment_count(), + len + ) + .as_str(), + )) + } else { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + } + + fn deserialize_enum( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.path.is_empty() { + Err(de::value::Error::custom("expected at least one parameters")) + } else { + visitor.visit_enum(ValueEnum { + value: &self.path[0], + }) + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.path.segment_count() != 1 { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected 1", + self.path.segment_count() + ) + .as_str(), + )) + } else { + visitor.visit_str(&self.path[0]) + } + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + + unsupported_type!(deserialize_any, "'any'"); + unsupported_type!(deserialize_bytes, "bytes"); + unsupported_type!(deserialize_option, "Option"); + unsupported_type!(deserialize_identifier, "identifier"); + unsupported_type!(deserialize_ignored_any, "ignored_any"); + + parse_single_value!(deserialize_bool, visit_bool, "bool"); + parse_single_value!(deserialize_i8, visit_i8, "i8"); + parse_single_value!(deserialize_i16, visit_i16, "i16"); + parse_single_value!(deserialize_i32, visit_i32, "i32"); + parse_single_value!(deserialize_i64, visit_i64, "i64"); + parse_single_value!(deserialize_u8, visit_u8, "u8"); + parse_single_value!(deserialize_u16, visit_u16, "u16"); + parse_single_value!(deserialize_u32, visit_u32, "u32"); + parse_single_value!(deserialize_u64, visit_u64, "u64"); + parse_single_value!(deserialize_f32, visit_f32, "f32"); + parse_single_value!(deserialize_f64, visit_f64, "f64"); + parse_single_value!(deserialize_string, visit_string, "String"); + parse_single_value!(deserialize_byte_buf, visit_string, "String"); + parse_single_value!(deserialize_char, visit_char, "char"); +} + +struct ParamsDeserializer<'de, T: ResourcePath> { + params: PathIter<'de, T>, + current: Option<(&'de str, &'de str)>, +} + +impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> { + type Error = de::value::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: de::DeserializeSeed<'de>, + { + self.current = self.params.next().map(|ref item| (item.0, item.1)); + match self.current { + Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + if let Some((_, value)) = self.current.take() { + seed.deserialize(Value { value }) + } else { + Err(de::value::Error::custom("unexpected item")) + } + } +} + +struct Key<'de> { + key: &'de str, +} + +impl<'de> Deserializer<'de> for Key<'de> { + type Error = de::value::Error; + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_str(self.key) + } + + fn deserialize_any(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("Unexpected")) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum ignored_any + } +} + +macro_rules! parse_value { + ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let v = self.value.parse().map_err(|_| { + de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) + })?; + visitor.$visit_fn(v) + } + }; +} + +struct Value<'de> { + value: &'de str, +} + +impl<'de> Deserializer<'de> for Value<'de> { + type Error = de::value::Error; + + parse_value!(deserialize_bool, visit_bool, "bool"); + parse_value!(deserialize_i8, visit_i8, "i8"); + parse_value!(deserialize_i16, visit_i16, "i16"); + parse_value!(deserialize_i32, visit_i32, "i16"); + parse_value!(deserialize_i64, visit_i64, "i64"); + parse_value!(deserialize_u8, visit_u8, "u8"); + parse_value!(deserialize_u16, visit_u16, "u16"); + parse_value!(deserialize_u32, visit_u32, "u32"); + parse_value!(deserialize_u64, visit_u64, "u64"); + parse_value!(deserialize_f32, visit_f32, "f32"); + parse_value!(deserialize_f64, visit_f64, "f64"); + parse_value!(deserialize_string, visit_string, "String"); + parse_value!(deserialize_byte_buf, visit_string, "String"); + parse_value!(deserialize_char, visit_char, "char"); + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_borrowed_bytes(self.value.as_bytes()) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_borrowed_str(self.value) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_enum( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_enum(ValueEnum { value: self.value }) + } + + fn deserialize_newtype_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple(self, _: usize, _: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: tuple")) + } + + fn deserialize_struct( + self, + _: &'static str, + _: &'static [&'static str], + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: struct")) + } + + fn deserialize_tuple_struct( + self, + _: &'static str, + _: usize, + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: tuple struct")) + } + + unsupported_type!(deserialize_any, "any"); + unsupported_type!(deserialize_seq, "seq"); + unsupported_type!(deserialize_map, "map"); + unsupported_type!(deserialize_identifier, "identifier"); +} + +struct ParamsSeq<'de, T: ResourcePath> { + params: PathIter<'de, T>, +} + +impl<'de, T: ResourcePath> de::SeqAccess<'de> for ParamsSeq<'de, T> { + type Error = de::value::Error; + + fn next_element_seed(&mut self, seed: U) -> Result, Self::Error> + where + U: de::DeserializeSeed<'de>, + { + match self.params.next() { + Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), + None => Ok(None), + } + } +} + +struct ValueEnum<'de> { + value: &'de str, +} + +impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { + type Error = de::value::Error; + type Variant = UnitVariant; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: de::DeserializeSeed<'de>, + { + Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) + } +} + +struct UnitVariant; + +impl<'de> de::VariantAccess<'de> for UnitVariant { + type Error = de::value::Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn struct_variant( + self, + _: &'static [&'static str], + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } +} + +#[cfg(test)] +mod tests { + use serde::{de, Deserialize}; + + use super::*; + use crate::path::Path; + use crate::router::Router; + + #[derive(Deserialize)] + struct MyStruct { + key: String, + value: String, + } + + #[derive(Deserialize)] + struct Id { + _id: String, + } + + #[derive(Debug, Deserialize)] + struct Test1(String, u32); + + #[derive(Debug, Deserialize)] + struct Test2 { + key: String, + value: u32, + } + + #[derive(Debug, Deserialize, PartialEq)] + #[serde(rename_all = "lowercase")] + enum TestEnum { + Val1, + Val2, + } + + #[derive(Debug, Deserialize)] + struct Test3 { + val: TestEnum, + } + + #[test] + fn test_request_extract() { + let mut router = Router::<()>::build(); + router.path("/{key}/{value}/", ()); + let router = router.finish(); + + let mut path = Path::new("/name/user1/"); + assert!(router.recognize(&mut path).is_some()); + + let s: MyStruct = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + + let s: (String, String) = + de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + + let mut router = Router::<()>::build(); + router.path("/{key}/{value}/", ()); + let router = router.finish(); + + let mut path = Path::new("/name/32/"); + assert!(router.recognize(&mut path).is_some()); + + let s: Test1 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + + let s: Test2 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, 32); + + let s: (String, u8) = + de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + + let res: Vec = + de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); + } + + #[test] + fn test_extract_path_single() { + let mut router = Router::<()>::build(); + router.path("/{value}/", ()); + let router = router.finish(); + + let mut path = Path::new("/32/"); + assert!(router.recognize(&mut path).is_some()); + let i: i8 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(i, 32); + } + + #[test] + fn test_extract_enum() { + let mut router = Router::<()>::build(); + router.path("/{val}/", ()); + let router = router.finish(); + + let mut path = Path::new("/val1/"); + assert!(router.recognize(&mut path).is_some()); + let i: TestEnum = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(i, TestEnum::Val1); + + let mut router = Router::<()>::build(); + router.path("/{val1}/{val2}/", ()); + let router = router.finish(); + + let mut path = Path::new("/val1/val2/"); + assert!(router.recognize(&mut path).is_some()); + let i: (TestEnum, TestEnum) = + de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(i, (TestEnum::Val1, TestEnum::Val2)); + } + + #[test] + fn test_extract_enum_value() { + let mut router = Router::<()>::build(); + router.path("/{val}/", ()); + let router = router.finish(); + + let mut path = Path::new("/val1/"); + assert!(router.recognize(&mut path).is_some()); + let i: Test3 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); + assert_eq!(i.val, TestEnum::Val1); + + let mut path = Path::new("/val3/"); + assert!(router.recognize(&mut path).is_some()); + let i: Result = + de::Deserialize::deserialize(PathDeserializer::new(&path)); + assert!(i.is_err()); + assert!(format!("{:?}", i).contains("unknown variant")); + } + + #[test] + fn test_extract_errors() { + let mut router = Router::<()>::build(); + router.path("/{value}/", ()); + let router = router.finish(); + + let mut path = Path::new("/name/"); + assert!(router.recognize(&mut path).is_some()); + + let s: Result = + de::Deserialize::deserialize(PathDeserializer::new(&path)); + assert!(s.is_err()); + assert!(format!("{:?}", s).contains("wrong number of parameters")); + + let s: Result = + de::Deserialize::deserialize(PathDeserializer::new(&path)); + assert!(s.is_err()); + assert!(format!("{:?}", s).contains("can not parse")); + + let s: Result<(String, String), de::value::Error> = + de::Deserialize::deserialize(PathDeserializer::new(&path)); + assert!(s.is_err()); + assert!(format!("{:?}", s).contains("wrong number of parameters")); + + let s: Result = + de::Deserialize::deserialize(PathDeserializer::new(&path)); + assert!(s.is_err()); + assert!(format!("{:?}", s).contains("can not parse")); + } + + // #[test] + // fn test_extract_path_decode() { + // let mut router = Router::<()>::default(); + // router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + + // macro_rules! test_single_value { + // ($value:expr, $expected:expr) => {{ + // let req = TestRequest::with_uri($value).finish(); + // let info = router.recognize(&req, &(), 0); + // let req = req.with_route_info(info); + // assert_eq!( + // *Path::::from_request(&req, &PathConfig::default()).unwrap(), + // $expected + // ); + // }}; + // } + + // test_single_value!("/%25/", "%"); + // test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+="); + // test_single_value!("/%2B/", "+"); + // test_single_value!("/%252B/", "%2B"); + // test_single_value!("/%2F/", "/"); + // test_single_value!("/%252F/", "%2F"); + // test_single_value!( + // "/http%3A%2F%2Flocalhost%3A80%2Ffoo/", + // "http://localhost:80/foo" + // ); + // test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog"); + // test_single_value!( + // "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/", + // "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog" + // ); + + // let req = TestRequest::with_uri("/%25/7/?id=test").finish(); + + // let mut router = Router::<()>::default(); + // router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + // let info = router.recognize(&req, &(), 0); + // let req = req.with_route_info(info); + + // let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); + // assert_eq!(s.key, "%"); + // assert_eq!(s.value, 7); + + // let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); + // assert_eq!(s.0, "%"); + // assert_eq!(s.1, "7"); + // } + + // #[test] + // fn test_extract_path_no_decode() { + // let mut router = Router::<()>::default(); + // router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + + // let req = TestRequest::with_uri("/%25/").finish(); + // let info = router.recognize(&req, &(), 0); + // let req = req.with_route_info(info); + // assert_eq!( + // *Path::::from_request(&req, &&PathConfig::default().disable_decoding()) + // .unwrap(), + // "%25" + // ); + // } +} diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs new file mode 100644 index 000000000..463e59e42 --- /dev/null +++ b/actix-router/src/lib.rs @@ -0,0 +1,149 @@ +//! Resource path matching and router. + +#![deny(rust_2018_idioms, nonstandard_style)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] + +mod de; +mod path; +mod resource; +mod router; + +pub use self::de::PathDeserializer; +pub use self::path::Path; +pub use self::resource::ResourceDef; +pub use self::router::{ResourceInfo, Router, RouterBuilder}; + +// TODO: this trait is necessary, document it +// see impl Resource for ServiceRequest +pub trait Resource { + fn resource_path(&mut self) -> &mut Path; +} + +pub trait ResourcePath { + fn path(&self) -> &str; +} + +impl ResourcePath for String { + fn path(&self) -> &str { + self.as_str() + } +} + +impl<'a> ResourcePath for &'a str { + fn path(&self) -> &str { + self + } +} + +impl ResourcePath for bytestring::ByteString { + fn path(&self) -> &str { + &*self + } +} + +/// One or many patterns. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Patterns { + Single(String), + List(Vec), +} + +impl Patterns { + pub fn is_empty(&self) -> bool { + match self { + Patterns::Single(_) => false, + Patterns::List(pats) => pats.is_empty(), + } + } +} + +/// Helper trait for type that could be converted to one or more path pattern. +pub trait IntoPatterns { + fn patterns(&self) -> Patterns; +} + +impl IntoPatterns for String { + fn patterns(&self) -> Patterns { + Patterns::Single(self.clone()) + } +} + +impl<'a> IntoPatterns for &'a String { + fn patterns(&self) -> Patterns { + Patterns::Single((*self).clone()) + } +} + +impl<'a> IntoPatterns for &'a str { + fn patterns(&self) -> Patterns { + Patterns::Single((*self).to_owned()) + } +} + +impl IntoPatterns for bytestring::ByteString { + fn patterns(&self) -> Patterns { + Patterns::Single(self.to_string()) + } +} + +impl IntoPatterns for Patterns { + fn patterns(&self) -> Patterns { + self.clone() + } +} + +impl> IntoPatterns for Vec { + fn patterns(&self) -> Patterns { + let mut patterns = self.iter().map(|v| v.as_ref().to_owned()); + + match patterns.size_hint() { + (1, _) => Patterns::Single(patterns.next().unwrap()), + _ => Patterns::List(patterns.collect()), + } + } +} + +macro_rules! array_patterns_single (($tp:ty) => { + impl IntoPatterns for [$tp; 1] { + fn patterns(&self) -> Patterns { + Patterns::Single(self[0].to_owned()) + } + } +}); + +macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => { + // for each array length specified in $num + $( + impl IntoPatterns for [$tp; $num] { + fn patterns(&self) -> Patterns { + Patterns::List(self.iter().map($str_fn).collect()) + } + } + )+ +}); + +array_patterns_single!(&str); +array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); + +array_patterns_single!(String); +array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); + +#[cfg(feature = "http")] +mod url; + +#[cfg(feature = "http")] +pub use self::url::{Quoter, Url}; + +#[cfg(feature = "http")] +mod http_impls { + use http::Uri; + + use super::ResourcePath; + + impl ResourcePath for Uri { + fn path(&self) -> &str { + self.path() + } + } +} diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs new file mode 100644 index 000000000..e29591f96 --- /dev/null +++ b/actix-router/src/path.rs @@ -0,0 +1,220 @@ +use std::borrow::Cow; +use std::ops::Index; + +use firestorm::profile_method; +use serde::de; + +use crate::{de::PathDeserializer, Resource, ResourcePath}; + +#[derive(Debug, Clone)] +pub(crate) enum PathItem { + Static(Cow<'static, str>), + Segment(u16, u16), +} + +impl Default for PathItem { + fn default() -> Self { + Self::Static(Cow::Borrowed("")) + } +} + +/// Resource path match information. +/// +/// If resource path contains variable patterns, `Path` stores them. +#[derive(Debug, Clone, Default)] +pub struct Path { + path: T, + pub(crate) skip: u16, + pub(crate) segments: Vec<(Cow<'static, str>, PathItem)>, +} + +impl Path { + pub fn new(path: T) -> Path { + Path { + path, + skip: 0, + segments: Vec::new(), + } + } + + /// Get reference to inner path instance. + #[inline] + pub fn get_ref(&self) -> &T { + &self.path + } + + /// Get mutable reference to inner path instance. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + &mut self.path + } + + /// Path. + #[inline] + pub fn path(&self) -> &str { + profile_method!(path); + + let skip = self.skip as usize; + let path = self.path.path(); + if skip <= path.len() { + &path[skip..] + } else { + "" + } + } + + /// Set new path. + #[inline] + pub fn set(&mut self, path: T) { + self.skip = 0; + self.path = path; + self.segments.clear(); + } + + /// Reset state. + #[inline] + pub fn reset(&mut self) { + self.skip = 0; + self.segments.clear(); + } + + /// Skip first `n` chars in path. + #[inline] + pub fn skip(&mut self, n: u16) { + self.skip += n; + } + + pub(crate) fn add(&mut self, name: impl Into>, value: PathItem) { + profile_method!(add); + + match value { + PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))), + PathItem::Segment(begin, end) => self.segments.push(( + name.into(), + PathItem::Segment(self.skip + begin, self.skip + end), + )), + } + } + + #[doc(hidden)] + pub fn add_static( + &mut self, + name: impl Into>, + value: impl Into>, + ) { + self.segments + .push((name.into(), PathItem::Static(value.into()))); + } + + /// Check if there are any matched patterns. + #[inline] + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } + + /// Returns number of interpolated segments. + #[inline] + pub fn segment_count(&self) -> usize { + self.segments.len() + } + + /// Get matched parameter by name without type conversion + pub fn get(&self, name: &str) -> Option<&str> { + profile_method!(get); + + for (seg_name, val) in self.segments.iter() { + if name == seg_name { + return match val { + PathItem::Static(ref s) => Some(&s), + PathItem::Segment(s, e) => { + Some(&self.path.path()[(*s as usize)..(*e as usize)]) + } + }; + } + } + + None + } + + /// Get unprocessed part of the path + pub fn unprocessed(&self) -> &str { + &self.path.path()[(self.skip as usize)..] + } + + /// Get matched parameter by name. + /// + /// If keyed parameter is not available empty string is used as default value. + pub fn query(&self, key: &str) -> &str { + profile_method!(query); + + if let Some(s) = self.get(key) { + s + } else { + "" + } + } + + /// Return iterator to items in parameter container. + pub fn iter(&self) -> PathIter<'_, T> { + PathIter { + idx: 0, + params: self, + } + } + + /// Try to deserialize matching parameters to a specified type `U` + pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result { + profile_method!(load); + de::Deserialize::deserialize(PathDeserializer::new(self)) + } +} + +#[derive(Debug)] +pub struct PathIter<'a, T> { + idx: usize, + params: &'a Path, +} + +impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option<(&'a str, &'a str)> { + if self.idx < self.params.segment_count() { + let idx = self.idx; + let res = match self.params.segments[idx].1 { + PathItem::Static(ref s) => &s, + PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)], + }; + self.idx += 1; + return Some((&self.params.segments[idx].0, res)); + } + None + } +} + +impl<'a, T: ResourcePath> Index<&'a str> for Path { + type Output = str; + + fn index(&self, name: &'a str) -> &str { + self.get(name) + .expect("Value for parameter is not available") + } +} + +impl Index for Path { + type Output = str; + + fn index(&self, idx: usize) -> &str { + match self.segments[idx].1 { + PathItem::Static(ref s) => &s, + PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)], + } + } +} + +impl Resource for Path { + fn resource_path(&mut self) -> &mut Self { + self + } +} diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs new file mode 100644 index 000000000..61ff587a5 --- /dev/null +++ b/actix-router/src/resource.rs @@ -0,0 +1,1803 @@ +use std::{ + borrow::{Borrow, Cow}, + collections::HashMap, + hash::{BuildHasher, Hash, Hasher}, + mem, +}; + +use firestorm::{profile_fn, profile_method, profile_section}; +use regex::{escape, Regex, RegexSet}; + +use crate::{ + path::{Path, PathItem}, + IntoPatterns, Patterns, Resource, ResourcePath, +}; + +const MAX_DYNAMIC_SEGMENTS: usize = 16; + +/// Regex flags to allow '.' in regex to match '\n' +/// +/// See the docs under: https://docs.rs/regex/1/regex/#grouping-and-flags +const REGEX_FLAGS: &str = "(?s-m)"; + +/// Describes the set of paths that match to a resource. +/// +/// `ResourceDef`s are effectively a way to transform the a custom resource pattern syntax into +/// suitable regular expressions from which to check matches with paths and capture portions of a +/// matched path into variables. Common cases are on a fast path that avoids going through the +/// regex engine. +/// +/// +/// # Static Resources +/// A static resource is the most basic type of definition. Pass a regular string to +/// [new][Self::new]. Conforming paths must match the string exactly. +/// +/// ## Examples +/// ``` +/// # use actix_router::ResourceDef; +/// let resource = ResourceDef::new("/home"); +/// +/// assert!(resource.is_match("/home")); +/// +/// assert!(!resource.is_match("/home/new")); +/// assert!(!resource.is_match("/homes")); +/// assert!(!resource.is_match("/search")); +/// ``` +/// +/// +/// # Dynamic Segments +/// Also known as "path parameters". Resources can define sections of a pattern that be extracted +/// from a conforming path, if it conforms to (one of) the resource pattern(s). +/// +/// The marker for a dynamic segment is curly braces wrapping an identifier. For example, +/// `/user/{id}` would match paths like `/user/123` or `/user/james` and be able to extract the user +/// IDs "123" and "james", respectively. +/// +/// However, this resource pattern (`/user/{id}`) would, not cover `/user/123/stars` (unless +/// constructed as a prefix; see next section) since the default pattern for segments matches all +/// characters until it finds a `/` character (or the end of the path). Custom segment patterns are +/// covered further down. +/// +/// Dynamic segments do not need to be delimited by `/` characters, they can be defined within a +/// path segment. For example, `/rust-is-{opinion}` can match the paths `/rust-is-cool` and +/// `/rust-is-hard`. +/// +/// For information on capturing segment values from paths or other custom resource types, +/// see [`capture_match_info`][Self::capture_match_info] +/// and [`capture_match_info_fn`][Self::capture_match_info_fn]. +/// +/// A resource definition can contain at most 16 dynamic segments. +/// +/// ## Examples +/// ``` +/// use actix_router::{Path, ResourceDef}; +/// +/// let resource = ResourceDef::prefix("/user/{id}"); +/// +/// assert!(resource.is_match("/user/123")); +/// assert!(!resource.is_match("/user")); +/// assert!(!resource.is_match("/user/")); +/// +/// let mut path = Path::new("/user/123"); +/// resource.capture_match_info(&mut path); +/// assert_eq!(path.get("id").unwrap(), "123"); +/// ``` +/// +/// +/// # Prefix Resources +/// A prefix resource is defined as pattern that can match just the start of a path. +/// +/// This library chooses to restrict that definition slightly. In particular, when matching, the +/// prefix must be separated from the remaining part of the path by a `/` character, either at the +/// end of the prefix pattern or at the start of the the remaining slice. In practice, this is not +/// much of a limitation. +/// +/// Prefix resources can contain dynamic segments. +/// +/// ## Examples +/// ``` +/// # use actix_router::ResourceDef; +/// let resource = ResourceDef::prefix("/home"); +/// assert!(resource.is_match("/home")); +/// assert!(resource.is_match("/home/new")); +/// assert!(!resource.is_match("/homes")); +/// +/// let resource = ResourceDef::prefix("/user/{id}/"); +/// assert!(resource.is_match("/user/123/")); +/// assert!(resource.is_match("/user/123/stars")); +/// ``` +/// +/// +/// # Custom Regex Segments +/// Dynamic segments can be customised to only match a specific regular expression. It can be +/// helpful to do this if resource definitions would otherwise conflict and cause one to +/// be inaccessible. +/// +/// The regex used when capturing segment values can be specified explicitly using this syntax: +/// `{name:regex}`. For example, `/user/{id:\d+}` will only match paths where the user ID +/// is numeric. +/// +/// By default, dynamic segments use this regex: `[^/]+`. This shows why it is the case, as shown in +/// the earlier section, that segments capture a slice of the path up to the next `/` character. +/// +/// Custom regex segments can be used in static and prefix resource definition variants. +/// +/// ## Examples +/// ``` +/// # use actix_router::ResourceDef; +/// let resource = ResourceDef::new(r"/user/{id:\d+}"); +/// assert!(resource.is_match("/user/123")); +/// assert!(resource.is_match("/user/314159")); +/// assert!(!resource.is_match("/user/abc")); +/// ``` +/// +/// +/// # Tail Segments +/// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those +/// up until a `/` character), there is a special pattern to match (and capture) the remaining +/// path portion. +/// +/// To do this, use the segment pattern: `{name}*`. Since a tail segment also has a name, values are +/// extracted in the same way as non-tail dynamic segments. +/// +/// ## Examples +/// ```rust +/// # use actix_router::{Path, ResourceDef}; +/// let resource = ResourceDef::new("/blob/{tail}*"); +/// assert!(resource.is_match("/blob/HEAD/Cargo.toml")); +/// assert!(resource.is_match("/blob/HEAD/README.md")); +/// +/// let mut path = Path::new("/blob/main/LICENSE"); +/// resource.capture_match_info(&mut path); +/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE"); +/// ``` +/// +/// +/// # Multi-Pattern Resources +/// For resources that can map to multiple distinct paths, it may be suitable to use +/// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined +/// into a regex set which is usually quicker to check matches on than checking each +/// pattern individually. +/// +/// Multi-pattern resources can contain dynamic segments just like single pattern ones. +/// However, take care to use consistent and semantically-equivalent segment names; it could affect +/// expectations in the router using these definitions and cause runtime panics. +/// +/// ## Examples +/// ```rust +/// # use actix_router::ResourceDef; +/// let resource = ResourceDef::new(["/home", "/index"]); +/// assert!(resource.is_match("/home")); +/// assert!(resource.is_match("/index")); +/// ``` +/// +/// +/// # Trailing Slashes +/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes. +/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if +/// they you wish to accommodate "recoverable" path errors. Below are several examples of +/// resource-path pairs that would not be compatible. +/// +/// ## Examples +/// ```rust +/// # use actix_router::ResourceDef; +/// assert!(!ResourceDef::new("/root").is_match("/root/")); +/// assert!(!ResourceDef::new("/root/").is_match("/root")); +/// assert!(!ResourceDef::prefix("/root/").is_match("/root")); +/// ``` +#[derive(Clone, Debug)] +pub struct ResourceDef { + id: u16, + + /// Optional name of resource. + name: Option, + + /// Pattern that generated the resource definition. + /// + /// `None` when pattern type is `DynamicSet`. + patterns: Patterns, + + /// Pattern type. + pat_type: PatternType, + + /// List of segments that compose the pattern, in order. + /// + /// `None` when pattern type is `DynamicSet`. + segments: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +enum PatternSegment { + /// Literal slice of pattern. + Const(String), + + /// Name of dynamic segment. + Var(String), +} + +#[derive(Clone, Debug)] +#[allow(clippy::large_enum_variant)] +enum PatternType { + /// Single constant/literal segment. + Static(String), + + /// Single constant/literal prefix segment. + Prefix(String), + + /// Single regular expression and list of dynamic segment names. + Dynamic(Regex, Vec<&'static str>), + + /// Regular expression set and list of component expressions plus dynamic segment names. + DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>), +} + +impl ResourceDef { + /// Constructs a new resource definition from patterns. + /// + /// Multi-pattern resources can be constructed by providing a slice (or vec) of patterns. + /// + /// # Panics + /// Panics if path pattern is malformed. + /// + /// # Examples + /// ``` + /// use actix_router::ResourceDef; + /// + /// let resource = ResourceDef::new("/user/{id}"); + /// assert!(resource.is_match("/user/123")); + /// assert!(!resource.is_match("/user/123/stars")); + /// assert!(!resource.is_match("user/1234")); + /// assert!(!resource.is_match("/foo")); + /// + /// let resource = ResourceDef::new(["/profile", "/user/{id}"]); + /// assert!(resource.is_match("/profile")); + /// assert!(resource.is_match("/user/123")); + /// assert!(!resource.is_match("user/123")); + /// assert!(!resource.is_match("/foo")); + /// ``` + pub fn new(paths: T) -> Self { + profile_method!(new); + + match paths.patterns() { + Patterns::Single(pattern) => ResourceDef::from_single_pattern(&pattern, false), + + // since zero length pattern sets are possible + // just return a useless `ResourceDef` + Patterns::List(patterns) if patterns.is_empty() => ResourceDef { + id: 0, + name: None, + patterns: Patterns::List(patterns), + pat_type: PatternType::DynamicSet(RegexSet::empty(), Vec::new()), + segments: None, + }, + + Patterns::List(patterns) => { + let mut re_set = Vec::with_capacity(patterns.len()); + let mut pattern_data = Vec::new(); + + for pattern in &patterns { + match ResourceDef::parse(&pattern, false, true) { + (PatternType::Dynamic(re, names), _) => { + re_set.push(re.as_str().to_owned()); + pattern_data.push((re, names)); + } + _ => unreachable!(), + } + } + + let pattern_re_set = RegexSet::new(re_set).unwrap(); + + ResourceDef { + id: 0, + name: None, + patterns: Patterns::List(patterns), + pat_type: PatternType::DynamicSet(pattern_re_set, pattern_data), + segments: None, + } + } + } + } + + /// Constructs a new resource definition using a string pattern that performs prefix matching. + /// + /// More specifically, the regular expressions generated for matching are different when using + /// this method vs using `new`; they will not be appended with the `$` meta-character that + /// matches the end of an input. + /// + /// Although it will compile and run correctly, it is meaningless to construct a prefix + /// resource definition with a tail segment; use [`new`][Self::new] in this case. + /// + /// # Panics + /// Panics if path regex pattern is malformed. + /// + /// # Examples + /// ``` + /// use actix_router::ResourceDef; + /// + /// let resource = ResourceDef::prefix("/user/{id}"); + /// assert!(resource.is_match("/user/123")); + /// assert!(resource.is_match("/user/123/stars")); + /// assert!(!resource.is_match("user/123")); + /// assert!(!resource.is_match("user/123/stars")); + /// assert!(!resource.is_match("/foo")); + /// + /// let resource = ResourceDef::prefix("user/{id}"); + /// assert!(resource.is_match("user/123")); + /// assert!(resource.is_match("user/123/stars")); + /// assert!(!resource.is_match("/user/123")); + /// assert!(!resource.is_match("/user/123/stars")); + /// assert!(!resource.is_match("foo")); + /// ``` + pub fn prefix(path: &str) -> Self { + profile_method!(prefix); + ResourceDef::from_single_pattern(path, true) + } + + /// Constructs a new resource definition using a string pattern that performs prefix matching, + /// inserting a `/` to beginning of the pattern if absent and pattern is not empty. + /// + /// # Panics + /// Panics if path regex pattern is malformed. + /// + /// # Examples + /// ``` + /// use actix_router::ResourceDef; + /// + /// let resource = ResourceDef::root_prefix("user/{id}"); + /// + /// assert_eq!(&resource, &ResourceDef::prefix("/user/{id}")); + /// assert_eq!(&resource, &ResourceDef::root_prefix("/user/{id}")); + /// assert_ne!(&resource, &ResourceDef::new("user/{id}")); + /// assert_ne!(&resource, &ResourceDef::new("/user/{id}")); + /// + /// assert!(resource.is_match("/user/123")); + /// assert!(!resource.is_match("user/123")); + /// ``` + pub fn root_prefix(path: &str) -> Self { + profile_method!(root_prefix); + ResourceDef::prefix(&insert_slash(path)) + } + + /// Returns a numeric resource ID. + /// + /// If not explicitly set using [`set_id`][Self::set_id], this will return `0`. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/root"); + /// assert_eq!(resource.id(), 0); + /// + /// resource.set_id(42); + /// assert_eq!(resource.id(), 42); + /// ``` + pub fn id(&self) -> u16 { + self.id + } + + /// Set numeric resource ID. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/root"); + /// resource.set_id(42); + /// assert_eq!(resource.id(), 42); + /// ``` + pub fn set_id(&mut self, id: u16) { + self.id = id; + } + + /// Returns resource definition name, if set. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/root"); + /// assert!(resource.name().is_none()); + /// + /// resource.set_name("root"); + /// assert_eq!(resource.name().unwrap(), "root"); + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Assigns a new name to the resource. + /// + /// # Panics + /// Panics if `name` is an empty string. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/root"); + /// resource.set_name("root"); + /// assert_eq!(resource.name().unwrap(), "root"); + /// ``` + pub fn set_name(&mut self, name: impl Into) { + let name = name.into(); + + if name.is_empty() { + panic!("resource name should not be empty"); + } + + self.name = Some(name) + } + + /// Returns `true` if pattern type is prefix. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// assert!(ResourceDef::prefix("/user").is_prefix()); + /// assert!(!ResourceDef::new("/user").is_prefix()); + /// ``` + pub fn is_prefix(&self) -> bool { + match &self.pat_type { + PatternType::Prefix(_) => true, + PatternType::Dynamic(re, _) if !re.as_str().ends_with('$') => true, + _ => false, + } + } + + /// Returns the pattern string that generated the resource definition. + /// + /// Returns `None` if definition was constructed with multiple patterns. + /// See [`patterns_iter`][Self::pattern_iter]. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/user/{id}"); + /// assert_eq!(resource.pattern().unwrap(), "/user/{id}"); + /// + /// let mut resource = ResourceDef::new(["/profile", "/user/{id}"]); + /// assert!(resource.pattern().is_none()); + pub fn pattern(&self) -> Option<&str> { + match &self.patterns { + Patterns::Single(pattern) => Some(pattern.as_str()), + Patterns::List(_) => None, + } + } + + /// Returns iterator of pattern strings that generated the resource definition. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut resource = ResourceDef::new("/root"); + /// let mut iter = resource.pattern_iter(); + /// assert_eq!(iter.next().unwrap(), "/root"); + /// assert!(iter.next().is_none()); + /// + /// let mut resource = ResourceDef::new(["/root", "/backup"]); + /// let mut iter = resource.pattern_iter(); + /// assert_eq!(iter.next().unwrap(), "/root"); + /// assert_eq!(iter.next().unwrap(), "/backup"); + /// assert!(iter.next().is_none()); + pub fn pattern_iter(&self) -> impl Iterator { + struct PatternIter<'a> { + patterns: &'a Patterns, + list_idx: usize, + done: bool, + } + + impl<'a> Iterator for PatternIter<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + match &self.patterns { + Patterns::Single(pattern) => { + if self.done { + return None; + } + + self.done = true; + Some(pattern.as_str()) + } + Patterns::List(patterns) if patterns.is_empty() => None, + Patterns::List(patterns) => match patterns.get(self.list_idx) { + Some(pattern) => { + self.list_idx += 1; + Some(pattern.as_str()) + } + None => { + // fast path future call + self.done = true; + None + } + }, + } + } + + fn size_hint(&self) -> (usize, Option) { + match &self.patterns { + Patterns::Single(_) => (1, Some(1)), + Patterns::List(patterns) => (patterns.len(), Some(patterns.len())), + } + } + } + + PatternIter { + patterns: &self.patterns, + list_idx: 0, + done: false, + } + } + + /// Joins two resources. + /// + /// Resulting resource is prefix if `other` is prefix. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let joined = ResourceDef::prefix("/root").join(&ResourceDef::prefix("/seg")); + /// assert_eq!(joined, ResourceDef::prefix("/root/seg")); + /// ``` + pub fn join(&self, other: &ResourceDef) -> ResourceDef { + let patterns = self + .pattern_iter() + .flat_map(move |this| other.pattern_iter().map(move |other| (this, other))) + .map(|(this, other)| [this, other].join("")) + .collect::>(); + + match patterns.len() { + 1 => ResourceDef::from_single_pattern(&patterns[0], other.is_prefix()), + _ => ResourceDef::new(patterns), + } + } + + /// Returns `true` if `path` matches this resource. + /// + /// The behavior of this method depends on how the `ResourceDef` was constructed. For example, + /// static resources will not be able to match as many paths as dynamic and prefix resources. + /// See [`ResourceDef`] struct docs for details on resource definition types. + /// + /// This method will always agree with [`find_match`][Self::find_match] on whether the path + /// matches or not. + /// + /// # Examples + /// ``` + /// use actix_router::ResourceDef; + /// + /// // static resource + /// let resource = ResourceDef::new("/user"); + /// assert!(resource.is_match("/user")); + /// assert!(!resource.is_match("/users")); + /// assert!(!resource.is_match("/user/123")); + /// assert!(!resource.is_match("/foo")); + /// + /// // dynamic resource + /// let resource = ResourceDef::new("/user/{user_id}"); + /// assert!(resource.is_match("/user/123")); + /// assert!(!resource.is_match("/user/123/stars")); + /// + /// // prefix resource + /// let resource = ResourceDef::prefix("/root"); + /// assert!(resource.is_match("/root")); + /// assert!(resource.is_match("/root/leaf")); + /// assert!(!resource.is_match("/roots")); + /// + /// // more examples are shown in the `ResourceDef` struct docs + /// ``` + #[inline] + pub fn is_match(&self, path: &str) -> bool { + profile_method!(is_match); + + // this function could be expressed as: + // `self.find_match(path).is_some()` + // but this skips some checks and uses potentially faster regex methods + + match self.pat_type { + PatternType::Static(ref s) => s == path, + + PatternType::Prefix(ref prefix) if prefix == path => true, + PatternType::Prefix(ref prefix) => is_strict_prefix(prefix, path), + + // dynamic prefix + PatternType::Dynamic(ref re, _) if !re.as_str().ends_with('$') => { + match re.find(path) { + // prefix matches exactly + Some(m) if m.end() == path.len() => true, + + // prefix matches part + Some(m) => is_strict_prefix(m.as_str(), path), + + // prefix does not match + None => false, + } + } + + PatternType::Dynamic(ref re, _) => re.is_match(path), + PatternType::DynamicSet(ref re, _) => re.is_match(path), + } + } + + /// Tries to match `path` to this resource, returning the position in the path where the + /// match ends. + /// + /// This method will always agree with [`is_match`][Self::is_match] on whether the path matches + /// or not. + /// + /// # Examples + /// ``` + /// use actix_router::ResourceDef; + /// + /// // static resource + /// let resource = ResourceDef::new("/user"); + /// assert_eq!(resource.find_match("/user"), Some(5)); + /// assert!(resource.find_match("/user/").is_none()); + /// assert!(resource.find_match("/user/123").is_none()); + /// assert!(resource.find_match("/foo").is_none()); + /// + /// // constant prefix resource + /// let resource = ResourceDef::prefix("/user"); + /// assert_eq!(resource.find_match("/user"), Some(5)); + /// assert_eq!(resource.find_match("/user/"), Some(5)); + /// assert_eq!(resource.find_match("/user/123"), Some(5)); + /// + /// // dynamic prefix resource + /// let resource = ResourceDef::prefix("/user/{id}"); + /// assert_eq!(resource.find_match("/user/123"), Some(9)); + /// assert_eq!(resource.find_match("/user/1234/"), Some(10)); + /// assert_eq!(resource.find_match("/user/12345/stars"), Some(11)); + /// assert!(resource.find_match("/user/").is_none()); + /// + /// // multi-pattern resource + /// let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); + /// assert_eq!(resource.find_match("/user/123"), Some(9)); + /// assert_eq!(resource.find_match("/profile/1234"), Some(13)); + /// ``` + pub fn find_match(&self, path: &str) -> Option { + profile_method!(find_match); + + match &self.pat_type { + PatternType::Static(segment) if path == segment => Some(segment.len()), + PatternType::Static(_) => None, + + PatternType::Prefix(prefix) if path == prefix => Some(prefix.len()), + PatternType::Prefix(prefix) if is_strict_prefix(prefix, path) => Some(prefix.len()), + PatternType::Prefix(_) => None, + + // dynamic prefix + PatternType::Dynamic(ref re, _) if !re.as_str().ends_with('$') => { + match re.find(path) { + // prefix matches exactly + Some(m) if m.end() == path.len() => Some(m.end()), + + // prefix matches part + Some(m) if is_strict_prefix(m.as_str(), path) => Some(m.end()), + + // prefix does not match + _ => None, + } + } + + PatternType::Dynamic(re, _) => re.find(path).map(|m| m.end()), + + PatternType::DynamicSet(re, params) => { + let idx = re.matches(path).into_iter().next()?; + let (ref pattern, _) = params[idx]; + pattern.find(path).map(|m| m.end()) + } + } + } + + /// Collects dynamic segment values into `path`. + /// + /// Returns `true` if `path` matches this resource. + /// + /// # Examples + /// ``` + /// use actix_router::{Path, ResourceDef}; + /// + /// let resource = ResourceDef::prefix("/user/{id}"); + /// let mut path = Path::new("/user/123/stars"); + /// assert!(resource.capture_match_info(&mut path)); + /// assert_eq!(path.get("id").unwrap(), "123"); + /// assert_eq!(path.unprocessed(), "/stars"); + /// + /// let resource = ResourceDef::new("/blob/{path}*"); + /// let mut path = Path::new("/blob/HEAD/Cargo.toml"); + /// assert!(resource.capture_match_info(&mut path)); + /// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml"); + /// assert_eq!(path.unprocessed(), ""); + /// ``` + pub fn capture_match_info(&self, path: &mut Path) -> bool { + profile_method!(capture_match_info); + self.capture_match_info_fn(path, |_, _| true, ()) + } + + /// Collects dynamic segment values into `resource` after matching paths and executing + /// check function. + /// + /// The check function is given a reference to the passed resource and optional arbitrary data. + /// This is useful if you want to conditionally match on some non-path related aspect of the + /// resource type. + /// + /// Returns `true` if resource path matches this resource definition _and_ satisfies the + /// given check function. + /// + /// # Examples + /// ``` + /// use actix_router::{Path, ResourceDef}; + /// + /// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool { + /// let admin_allowed = std::env::var("ADMIN_ALLOWED").ok(); + /// + /// resource.capture_match_info_fn( + /// path, + /// // when env var is not set, reject when path contains "admin" + /// |res, admin_allowed| !res.path().contains("admin"), + /// &admin_allowed + /// ) + /// } + /// + /// let resource = ResourceDef::prefix("/user/{id}"); + /// + /// // path matches; segment values are collected into path + /// let mut path = Path::new("/user/james/stars"); + /// assert!(try_match(&resource, &mut path)); + /// assert_eq!(path.get("id").unwrap(), "james"); + /// assert_eq!(path.unprocessed(), "/stars"); + /// + /// // path matches but fails check function; no segments are collected + /// let mut path = Path::new("/user/admin/stars"); + /// assert!(!try_match(&resource, &mut path)); + /// assert_eq!(path.unprocessed(), "/user/admin/stars"); + /// ``` + pub fn capture_match_info_fn( + &self, + resource: &mut R, + check_fn: F, + user_data: U, + ) -> bool + where + R: Resource, + T: ResourcePath, + F: FnOnce(&R, U) -> bool, + { + profile_method!(capture_match_info_fn); + + let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default(); + let path = resource.resource_path(); + let path_str = path.path(); + + let (matched_len, matched_vars) = match &self.pat_type { + PatternType::Static(_) | PatternType::Prefix(_) => { + profile_section!(pattern_static_or_prefix); + + match self.find_match(path_str) { + Some(len) => (len, None), + None => return false, + } + } + + PatternType::Dynamic(re, names) => { + profile_section!(pattern_dynamic); + + let captures = { + profile_section!(pattern_dynamic_regex_exec); + + match re.captures(path.path()) { + Some(captures) => captures, + _ => return false, + } + }; + + { + profile_section!(pattern_dynamic_extract_captures); + + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(&name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); + } else { + log::error!( + "Dynamic path match but not all segments found: {}", + name + ); + return false; + } + } + }; + + (captures[0].len(), Some(names)) + } + + PatternType::DynamicSet(re, params) => { + profile_section!(pattern_dynamic_set); + + let path = path.path(); + let (pattern, names) = match re.matches(path).into_iter().next() { + Some(idx) => ¶ms[idx], + _ => return false, + }; + + let captures = match pattern.captures(path.path()) { + Some(captures) => captures, + _ => return false, + }; + + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(&name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); + } else { + log::error!("Dynamic path match but not all segments found: {}", name); + return false; + } + } + + (captures[0].len(), Some(names)) + } + }; + + if !check_fn(resource, user_data) { + return false; + } + + // Modify `path` to skip matched part and store matched segments + let path = resource.resource_path(); + + if let Some(vars) = matched_vars { + for i in 0..vars.len() { + path.add(vars[i], mem::take(&mut segments[i])); + } + } + + path.skip(matched_len as u16); + + true + } + + /// Assembles resource path using a closure that maps variable segment names to values. + fn build_resource_path(&self, path: &mut String, mut vars: F) -> bool + where + F: FnMut(&str) -> Option, + I: AsRef, + { + for el in match self.segments { + Some(ref segments) => segments, + None => return false, + } { + match *el { + PatternSegment::Const(ref val) => path.push_str(val), + PatternSegment::Var(ref name) => match vars(name) { + Some(val) => path.push_str(val.as_ref()), + _ => return false, + }, + } + } + + true + } + + /// Assembles full resource path from iterator of dynamic segment values. + /// + /// Returns `true` on success. + /// + /// Resource paths can not be built from multi-pattern resources; this call will always return + /// false and will not add anything to the string buffer. + /// + /// # Examples + /// ``` + /// # use actix_router::ResourceDef; + /// let mut s = String::new(); + /// let resource = ResourceDef::new("/user/{id}/post/{title}"); + /// + /// assert!(resource.resource_path_from_iter(&mut s, &["123", "my-post"])); + /// assert_eq!(s, "/user/123/post/my-post"); + /// ``` + pub fn resource_path_from_iter(&self, path: &mut String, values: I) -> bool + where + I: IntoIterator, + I::Item: AsRef, + { + profile_method!(resource_path_from_iter); + let mut iter = values.into_iter(); + self.build_resource_path(path, |_| iter.next()) + } + + /// Assembles resource path from map of dynamic segment values. + /// + /// Returns `true` on success. + /// + /// Resource paths can not be built from multi-pattern resources; this call will always return + /// false and will not add anything to the string buffer. + /// + /// # Examples + /// ``` + /// # use std::collections::HashMap; + /// # use actix_router::ResourceDef; + /// let mut s = String::new(); + /// let resource = ResourceDef::new("/user/{id}/post/{title}"); + /// + /// let mut map = HashMap::new(); + /// map.insert("id", "123"); + /// map.insert("title", "my-post"); + /// + /// assert!(resource.resource_path_from_map(&mut s, &map)); + /// assert_eq!(s, "/user/123/post/my-post"); + /// ``` + pub fn resource_path_from_map( + &self, + path: &mut String, + values: &HashMap, + ) -> bool + where + K: Borrow + Eq + Hash, + V: AsRef, + S: BuildHasher, + { + profile_method!(resource_path_from_map); + self.build_resource_path(path, |name| values.get(name).map(AsRef::::as_ref)) + } + + /// Parse path pattern and create a new instance. + fn from_single_pattern(pattern: &str, is_prefix: bool) -> Self { + profile_method!(from_single_pattern); + + let pattern = pattern.to_owned(); + let (pat_type, segments) = ResourceDef::parse(&pattern, is_prefix, false); + + ResourceDef { + id: 0, + name: None, + patterns: Patterns::Single(pattern), + pat_type, + segments: Some(segments), + } + } + + /// Parses a dynamic segment definition from a pattern. + /// + /// The returned tuple includes: + /// - the segment descriptor, either `Var` or `Tail` + /// - the segment's regex to check values against + /// - the remaining, unprocessed string slice + /// - whether the parsed parameter represents a tail pattern + /// + /// # Panics + /// Panics if given patterns does not contain a dynamic segment. + fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) { + profile_method!(parse_param); + + const DEFAULT_PATTERN: &str = "[^/]+"; + const DEFAULT_PATTERN_TAIL: &str = ".*"; + + let mut params_nesting = 0usize; + let close_idx = pattern + .find(|c| match c { + '{' => { + params_nesting += 1; + false + } + '}' => { + params_nesting -= 1; + params_nesting == 0 + } + _ => false, + }) + .unwrap_or_else(|| { + panic!(r#"path "{}" contains malformed dynamic segment"#, pattern) + }); + + let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1); + + // remove outer curly brackets + param = ¶m[1..param.len() - 1]; + + let tail = unprocessed == "*"; + + let (name, pattern) = match param.find(':') { + Some(idx) => { + if tail { + panic!("custom regex is not supported for tail match"); + } + + let (name, pattern) = param.split_at(idx); + (name, &pattern[1..]) + } + None => ( + param, + if tail { + unprocessed = &unprocessed[1..]; + DEFAULT_PATTERN_TAIL + } else { + DEFAULT_PATTERN + }, + ), + }; + + let segment = PatternSegment::Var(name.to_string()); + let regex = format!(r"(?P<{}>{})", &name, &pattern); + + (segment, regex, unprocessed, tail) + } + + /// Parse `pattern` using `is_prefix` and `force_dynamic` flags. + /// + /// Parameters: + /// - `is_prefix`: Use `true` if `pattern` should be treated as a prefix; i.e., a conforming + /// path will be a match even if it has parts remaining to process + /// - `force_dynamic`: Use `true` to disallow the return of static and prefix segments. + /// + /// The returned tuple includes: + /// - the pattern type detected, either `Static`, `Prefix`, or `Dynamic` + /// - a list of segment descriptors from the pattern + fn parse( + pattern: &str, + is_prefix: bool, + force_dynamic: bool, + ) -> (PatternType, Vec) { + profile_method!(parse); + + let mut unprocessed = pattern; + + if !force_dynamic && unprocessed.find('{').is_none() && !unprocessed.ends_with('*') { + // pattern is static + + let tp = if is_prefix { + PatternType::Prefix(unprocessed.to_owned()) + } else { + PatternType::Static(unprocessed.to_owned()) + }; + + return (tp, vec![PatternSegment::Const(unprocessed.to_owned())]); + } + + let mut segments = Vec::new(); + let mut re = format!("{}^", REGEX_FLAGS); + let mut dyn_segment_count = 0; + let mut has_tail_segment = false; + + while let Some(idx) = unprocessed.find('{') { + let (prefix, rem) = unprocessed.split_at(idx); + + segments.push(PatternSegment::Const(prefix.to_owned())); + re.push_str(&escape(prefix)); + + let (param_pattern, re_part, rem, tail) = Self::parse_param(rem); + + if tail { + has_tail_segment = true; + } + + segments.push(param_pattern); + re.push_str(&re_part); + + unprocessed = rem; + dyn_segment_count += 1; + } + + if is_prefix && has_tail_segment { + // tail segments in prefixes have no defined semantics + + #[cfg(not(test))] + log::warn!( + "Prefix resources should not have tail segments. \ + Use `ResourceDef::new` constructor. \ + This may become a panic in the future." + ); + + // panic in tests to make this case detectable + #[cfg(test)] + panic!("prefix resource definitions should not have tail segments"); + } + + if unprocessed.ends_with('*') { + // unnamed tail segment + + #[cfg(not(test))] + log::warn!( + "Tail segments must have names. \ + Consider `.../{{tail}}*`. \ + This may become a panic in the future." + ); + + // panic in tests to make this case detectable + #[cfg(test)] + panic!("tail segments must have names"); + } else if !has_tail_segment && !unprocessed.is_empty() { + // prevent `Const("")` element from being added after last dynamic segment + + segments.push(PatternSegment::Const(unprocessed.to_owned())); + re.push_str(&escape(unprocessed)); + } + + if dyn_segment_count > MAX_DYNAMIC_SEGMENTS { + panic!( + "Only {} dynamic segments are allowed, provided: {}", + MAX_DYNAMIC_SEGMENTS, dyn_segment_count + ); + } + + if !is_prefix && !has_tail_segment { + re.push('$'); + } + + let re = match Regex::new(&re) { + Ok(re) => re, + Err(err) => panic!("Wrong path pattern: \"{}\" {}", pattern, err), + }; + + // `Bok::leak(Box::new(name))` is an intentional memory leak. In typical applications the + // routing table is only constructed once (per worker) so leak is bounded. If you are + // constructing `ResourceDef`s more than once in your application's lifecycle you would + // expect a linear increase in leaked memory over time. + let names = re + .capture_names() + .filter_map(|name| name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())) + .collect(); + + (PatternType::Dynamic(re, names), segments) + } +} + +impl Eq for ResourceDef {} + +impl PartialEq for ResourceDef { + fn eq(&self, other: &ResourceDef) -> bool { + self.patterns == other.patterns + && match &self.pat_type { + PatternType::Static(_) => matches!(&other.pat_type, PatternType::Static(_)), + PatternType::Prefix(_) => matches!(&other.pat_type, PatternType::Prefix(_)), + PatternType::Dynamic(re, _) => match &other.pat_type { + PatternType::Dynamic(other_re, _) => re.as_str() == other_re.as_str(), + _ => false, + }, + PatternType::DynamicSet(_, _) => { + matches!(&other.pat_type, PatternType::DynamicSet(..)) + } + } + } +} + +impl Hash for ResourceDef { + fn hash(&self, state: &mut H) { + self.patterns.hash(state); + } +} + +impl<'a> From<&'a str> for ResourceDef { + fn from(path: &'a str) -> ResourceDef { + ResourceDef::new(path) + } +} + +impl From for ResourceDef { + fn from(path: String) -> ResourceDef { + ResourceDef::new(path) + } +} + +pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { + profile_fn!(insert_slash); + + if !path.is_empty() && !path.starts_with('/') { + let mut new_path = String::with_capacity(path.len() + 1); + new_path.push('/'); + new_path.push_str(path); + Cow::Owned(new_path) + } else { + Cow::Borrowed(path) + } +} + +/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. +/// +/// The `strict` refers to the fact that this will return `false` if `prefix == path`. +fn is_strict_prefix(prefix: &str, path: &str) -> bool { + path.starts_with(prefix) && (prefix.ends_with('/') || path[prefix.len()..].starts_with('/')) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn equivalence() { + assert_eq!( + ResourceDef::root_prefix("/root"), + ResourceDef::prefix("/root") + ); + assert_eq!( + ResourceDef::root_prefix("root"), + ResourceDef::prefix("/root") + ); + assert_eq!( + ResourceDef::root_prefix("/{id}"), + ResourceDef::prefix("/{id}") + ); + assert_eq!( + ResourceDef::root_prefix("{id}"), + ResourceDef::prefix("/{id}") + ); + + assert_eq!(ResourceDef::new("/"), ResourceDef::new(["/"])); + assert_eq!(ResourceDef::new("/"), ResourceDef::new(vec!["/"])); + + assert_ne!(ResourceDef::new(""), ResourceDef::prefix("")); + assert_ne!(ResourceDef::new("/"), ResourceDef::prefix("/")); + assert_ne!(ResourceDef::new("/{id}"), ResourceDef::prefix("/{id}")); + } + + #[test] + fn parse_static() { + let re = ResourceDef::new(""); + + assert!(!re.is_prefix()); + + assert!(re.is_match("")); + assert!(!re.is_match("/")); + assert_eq!(re.find_match(""), Some(0)); + assert_eq!(re.find_match("/"), None); + + let re = ResourceDef::new("/"); + assert!(re.is_match("/")); + assert!(!re.is_match("")); + assert!(!re.is_match("/foo")); + + let re = ResourceDef::new("/name"); + assert!(re.is_match("/name")); + assert!(!re.is_match("/name1")); + assert!(!re.is_match("/name/")); + assert!(!re.is_match("/name~")); + + let mut path = Path::new("/name"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), ""); + + assert_eq!(re.find_match("/name"), Some(5)); + assert_eq!(re.find_match("/name1"), None); + assert_eq!(re.find_match("/name/"), None); + assert_eq!(re.find_match("/name~"), None); + + let re = ResourceDef::new("/name/"); + assert!(re.is_match("/name/")); + assert!(!re.is_match("/name")); + assert!(!re.is_match("/name/gs")); + + let re = ResourceDef::new("/user/profile"); + assert!(re.is_match("/user/profile")); + assert!(!re.is_match("/user/profile/profile")); + + let mut path = Path::new("/user/profile"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), ""); + } + + #[test] + fn parse_param() { + let re = ResourceDef::new("/user/{id}"); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(!re.is_match("/user/2345/")); + assert!(!re.is_match("/user/2345/sdg")); + + let mut path = Path::new("/user/profile"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "profile"); + assert_eq!(path.unprocessed(), ""); + + let mut path = Path::new("/user/1245125"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "1245125"); + assert_eq!(path.unprocessed(), ""); + + let re = ResourceDef::new("/v{version}/resource/{id}"); + assert!(re.is_match("/v1/resource/320120")); + assert!(!re.is_match("/v/resource/1")); + assert!(!re.is_match("/resource")); + + let mut path = Path::new("/v151/resource/adage32"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("version").unwrap(), "151"); + assert_eq!(path.get("id").unwrap(), "adage32"); + assert_eq!(path.unprocessed(), ""); + + let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); + assert!(re.is_match("/012345")); + assert!(!re.is_match("/012")); + assert!(!re.is_match("/01234567")); + assert!(!re.is_match("/XXXXXX")); + + let mut path = Path::new("/012345"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "012345"); + assert_eq!(path.unprocessed(), ""); + } + + #[allow(clippy::cognitive_complexity)] + #[test] + fn dynamic_set() { + let re = ResourceDef::new(vec![ + "/user/{id}", + "/v{version}/resource/{id}", + "/{id:[[:digit:]]{6}}", + "/static", + ]); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(!re.is_match("/user/2345/")); + assert!(!re.is_match("/user/2345/sdg")); + + let mut path = Path::new("/user/profile"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "profile"); + assert_eq!(path.unprocessed(), ""); + + let mut path = Path::new("/user/1245125"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "1245125"); + assert_eq!(path.unprocessed(), ""); + + assert!(re.is_match("/v1/resource/320120")); + assert!(!re.is_match("/v/resource/1")); + assert!(!re.is_match("/resource")); + + let mut path = Path::new("/v151/resource/adage32"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("version").unwrap(), "151"); + assert_eq!(path.get("id").unwrap(), "adage32"); + + assert!(re.is_match("/012345")); + assert!(!re.is_match("/012")); + assert!(!re.is_match("/01234567")); + assert!(!re.is_match("/XXXXXX")); + + assert!(re.is_match("/static")); + assert!(!re.is_match("/a/static")); + assert!(!re.is_match("/static/a")); + + let mut path = Path::new("/012345"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "012345"); + + let re = ResourceDef::new([ + "/user/{id}", + "/v{version}/resource/{id}", + "/{id:[[:digit:]]{6}}", + ]); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(!re.is_match("/user/2345/")); + assert!(!re.is_match("/user/2345/sdg")); + + let re = ResourceDef::new([ + "/user/{id}".to_string(), + "/v{version}/resource/{id}".to_string(), + "/{id:[[:digit:]]{6}}".to_string(), + ]); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(!re.is_match("/user/2345/")); + assert!(!re.is_match("/user/2345/sdg")); + } + + #[test] + fn parse_tail() { + let re = ResourceDef::new("/user/-{id}*"); + + let mut path = Path::new("/user/-profile"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "profile"); + + let mut path = Path::new("/user/-2345"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345"); + + let mut path = Path::new("/user/-2345/"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345/"); + + let mut path = Path::new("/user/-2345/sdg"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345/sdg"); + } + + #[test] + fn static_tail() { + let re = ResourceDef::new("/user{tail}*"); + assert!(re.is_match("/users")); + assert!(re.is_match("/user-foo")); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(re.is_match("/user/2345/")); + assert!(re.is_match("/user/2345/sdg")); + assert!(!re.is_match("/foo/profile")); + + let re = ResourceDef::new("/user/{tail}*"); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(re.is_match("/user/2345/")); + assert!(re.is_match("/user/2345/sdg")); + assert!(!re.is_match("/foo/profile")); + } + + #[test] + fn dynamic_tail() { + let re = ResourceDef::new("/user/{id}/{tail}*"); + assert!(!re.is_match("/user/2345")); + let mut path = Path::new("/user/2345/sdg"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345"); + assert_eq!(path.get("tail").unwrap(), "sdg"); + assert_eq!(path.unprocessed(), ""); + } + + #[test] + fn newline_patterns_and_paths() { + let re = ResourceDef::new("/user/a\nb"); + assert!(re.is_match("/user/a\nb")); + assert!(!re.is_match("/user/a\nb/profile")); + + let re = ResourceDef::new("/a{x}b/test/a{y}b"); + let mut path = Path::new("/a\nb/test/a\nb"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("x").unwrap(), "\n"); + assert_eq!(path.get("y").unwrap(), "\n"); + + let re = ResourceDef::new("/user/{tail}*"); + assert!(re.is_match("/user/a\nb/")); + + let re = ResourceDef::new("/user/{id}*"); + let mut path = Path::new("/user/a\nb/a\nb"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "a\nb/a\nb"); + + let re = ResourceDef::new("/user/{id:.*}"); + let mut path = Path::new("/user/a\nb/a\nb"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "a\nb/a\nb"); + } + + #[cfg(feature = "http")] + #[test] + fn parse_urlencoded_param() { + use std::convert::TryFrom; + + let re = ResourceDef::new("/user/{id}/test"); + + let mut path = Path::new("/user/2345/test"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345"); + + let mut path = Path::new("/user/qwe%25/test"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "qwe%25"); + + let uri = http::Uri::try_from("/user/qwe%25/test").unwrap(); + let mut path = Path::new(uri); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "qwe%25"); + } + + #[test] + fn prefix_static() { + let re = ResourceDef::prefix("/name"); + + assert!(re.is_prefix()); + + assert!(re.is_match("/name")); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/test/test")); + assert!(!re.is_match("/name1")); + assert!(!re.is_match("/name~")); + + let mut path = Path::new("/name"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), ""); + + let mut path = Path::new("/name/test"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), "/test"); + + assert_eq!(re.find_match("/name"), Some(5)); + assert_eq!(re.find_match("/name/"), Some(5)); + assert_eq!(re.find_match("/name/test/test"), Some(5)); + assert_eq!(re.find_match("/name1"), None); + assert_eq!(re.find_match("/name~"), None); + + let re = ResourceDef::prefix("/name/"); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/gs")); + assert!(!re.is_match("/name")); + + let mut path = Path::new("/name/gs"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), "gs"); + + let re = ResourceDef::root_prefix("name/"); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/gs")); + assert!(!re.is_match("/name")); + + let mut path = Path::new("/name/gs"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.unprocessed(), "gs"); + } + + #[test] + fn prefix_dynamic() { + let re = ResourceDef::prefix("/{name}/"); + + assert!(re.is_prefix()); + + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/gs")); + assert!(!re.is_match("/name")); + + assert_eq!(re.find_match("/name/"), Some(6)); + assert_eq!(re.find_match("/name/gs"), Some(6)); + assert_eq!(re.find_match("/name"), None); + + let mut path = Path::new("/test2/"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(&path["name"], "test2"); + assert_eq!(&path[0], "test2"); + assert_eq!(path.unprocessed(), ""); + + let mut path = Path::new("/test2/subpath1/subpath2/index.html"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(&path["name"], "test2"); + assert_eq!(&path[0], "test2"); + assert_eq!(path.unprocessed(), "subpath1/subpath2/index.html"); + + let resource = ResourceDef::prefix("/user"); + // input string shorter than prefix + assert!(resource.find_match("/foo").is_none()); + } + + #[test] + fn build_path_list() { + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/test"); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["user1"]).iter())); + assert_eq!(s, "/user/user1/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/test"); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}"); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/"); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(!resource.resource_path_from_iter(&mut s, &mut (&["item"]).iter())); + + let mut s = String::new(); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/"); + assert!(!resource.resource_path_from_iter(&mut s, &mut (&["item"]).iter())); + + let mut s = String::new(); + assert!(resource.resource_path_from_iter(&mut s, &mut vec!["item", "item2"].iter())); + assert_eq!(s, "/user/item/item2/"); + } + + #[test] + fn multi_pattern_cannot_build_path() { + let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); + let mut s = String::new(); + assert!(!resource.resource_path_from_iter(&mut s, &mut ["123"].iter())); + } + + #[test] + fn multi_pattern_capture_segment_values() { + let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); + + let mut path = Path::new("/user/123"); + assert!(resource.capture_match_info(&mut path)); + assert!(path.get("id").is_some()); + + let mut path = Path::new("/profile/123"); + assert!(resource.capture_match_info(&mut path)); + assert!(path.get("id").is_some()); + + let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]); + + let mut path = Path::new("/user/123"); + assert!(resource.capture_match_info(&mut path)); + assert!(path.get("id").is_some()); + assert!(path.get("uid").is_none()); + + let mut path = Path::new("/profile/123"); + assert!(resource.capture_match_info(&mut path)); + assert!(path.get("id").is_none()); + assert!(path.get("uid").is_some()); + } + + #[test] + fn dynamic_prefix_proper_segmentation() { + let resource = ResourceDef::prefix(r"/id/{id:\d{3}}"); + + assert!(resource.is_match("/id/123")); + assert!(resource.is_match("/id/123/foo")); + assert!(!resource.is_match("/id/1234")); + assert!(!resource.is_match("/id/123a")); + + assert_eq!(resource.find_match("/id/123"), Some(7)); + assert_eq!(resource.find_match("/id/123/foo"), Some(7)); + assert_eq!(resource.find_match("/id/1234"), None); + assert_eq!(resource.find_match("/id/123a"), None); + } + + #[test] + fn build_path_map() { + let resource = ResourceDef::new("/user/{item1}/{item2}/"); + + let mut map = HashMap::new(); + map.insert("item1", "item"); + + let mut s = String::new(); + assert!(!resource.resource_path_from_map(&mut s, &map)); + + map.insert("item2", "item2"); + + let mut s = String::new(); + assert!(resource.resource_path_from_map(&mut s, &map)); + assert_eq!(s, "/user/item/item2/"); + } + + #[test] + fn build_path_tail() { + let resource = ResourceDef::new("/user/{item1}*"); + + let mut s = String::new(); + assert!(!resource.resource_path_from_iter(&mut s, &mut (&[""; 0]).iter())); + + let mut s = String::new(); + assert!(resource.resource_path_from_iter(&mut s, &mut (&["user1"]).iter())); + assert_eq!(s, "/user/user1"); + + let mut s = String::new(); + let mut map = HashMap::new(); + map.insert("item1", "item"); + assert!(resource.resource_path_from_map(&mut s, &map)); + assert_eq!(s, "/user/item"); + } + + #[test] + fn consistent_match_length() { + let result = Some(5); + + let re = ResourceDef::prefix("/abc/"); + assert_eq!(re.find_match("/abc/def"), result); + + let re = ResourceDef::prefix("/{id}/"); + assert_eq!(re.find_match("/abc/def"), result); + } + + #[test] + fn join() { + // test joined defs match the same paths as each component separately + + fn seq_find_match(re1: &ResourceDef, re2: &ResourceDef, path: &str) -> Option { + let len1 = re1.find_match(path)?; + let len2 = re2.find_match(&path[len1..])?; + Some(len1 + len2) + } + + macro_rules! join_test { + ($pat1:expr, $pat2:expr => $($test:expr),+) => {{ + let pat1 = $pat1; + let pat2 = $pat2; + $({ + let _path = $test; + let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::new(pat2)); + let _seq = seq_find_match(&re1, &re2, _path); + let _join = re1.join(&re2).find_match(_path); + assert_eq!( + _seq, _join, + "patterns: prefix {:?}, {:?}; mismatch on \"{}\"; seq={:?}; join={:?}", + pat1, pat2, _path, _seq, _join + ); + assert!(!re1.join(&re2).is_prefix()); + + let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::prefix(pat2)); + let _seq = seq_find_match(&re1, &re2, _path); + let _join = re1.join(&re2).find_match(_path); + assert_eq!( + _seq, _join, + "patterns: prefix {:?}, prefix {:?}; mismatch on \"{}\"; seq={:?}; join={:?}", + pat1, pat2, _path, _seq, _join + ); + assert!(re1.join(&re2).is_prefix()); + })+ + }} + } + + join_test!("", "" => "", "/hello", "/"); + join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123"); + join_test!("", "/user"=> "", "/user", "foo", "/user11", "user", "user/123"); + join_test!("/user", "/xx"=> "", "", "/", "/user", "/xx", "/userxx", "/user/xx"); + } + + #[test] + fn match_methods_agree() { + macro_rules! match_methods_agree { + ($pat:expr => $($test:expr),+) => {{ + match_methods_agree!(finish $pat, ResourceDef::new($pat), $($test),+); + }}; + (prefix $pat:expr => $($test:expr),+) => {{ + match_methods_agree!(finish $pat, ResourceDef::prefix($pat), $($test),+); + }}; + (finish $pat:expr, $re:expr, $($test:expr),+) => {{ + let re = $re; + $({ + let _is = re.is_match($test); + let _find = re.find_match($test).is_some(); + assert_eq!( + _is, _find, + "pattern: {:?}; mismatch on \"{}\"; is={}; find={}", + $pat, $test, _is, _find + ); + })+ + }} + } + + match_methods_agree!("" => "", "/", "/foo"); + match_methods_agree!("/" => "", "/", "/foo"); + match_methods_agree!("/user" => "user", "/user", "/users", "/user/123", "/foo"); + match_methods_agree!("/v{v}" => "v", "/v", "/v1", "/v222", "/foo"); + match_methods_agree!(["/v{v}", "/version/{v}"] => "/v", "/v1", "/version", "/version/1", "/foo"); + + match_methods_agree!("/path{tail}*" => "/path", "/path1", "/path/123"); + match_methods_agree!("/path/{tail}*" => "/path", "/path1", "/path/123"); + + match_methods_agree!(prefix "" => "", "/", "/foo"); + match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo"); + match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234"); + } + + #[test] + #[should_panic] + fn invalid_dynamic_segment_delimiter() { + ResourceDef::new("/user/{username"); + } + + #[test] + #[should_panic] + fn invalid_dynamic_segment_name() { + ResourceDef::new("/user/{}"); + } + + #[test] + #[should_panic] + fn invalid_too_many_dynamic_segments() { + // valid + ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}"); + + // panics + ResourceDef::new( + "/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}", + ); + } + + #[test] + #[should_panic] + fn invalid_custom_regex_for_tail() { + ResourceDef::new(r"/{tail:\d+}*"); + } + + #[test] + #[should_panic] + fn invalid_unnamed_tail_segment() { + ResourceDef::new("/*"); + } + + #[test] + #[should_panic] + fn prefix_plus_tail_match_is_allowed() { + ResourceDef::prefix("/user/{id}*"); + } +} diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs new file mode 100644 index 000000000..f5deb8583 --- /dev/null +++ b/actix-router/src/router.rs @@ -0,0 +1,281 @@ +use firestorm::profile_method; + +use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ResourceId(pub u16); + +/// Information about current resource +#[derive(Clone, Debug)] +pub struct ResourceInfo { + resource: ResourceId, +} + +/// Resource router. +// T is the resource itself +// U is any other data needed for routing like method guards +pub struct Router { + routes: Vec<(ResourceDef, T, Option)>, +} + +impl Router { + pub fn build() -> RouterBuilder { + RouterBuilder { + resources: Vec::new(), + } + } + + pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> + where + R: Resource

, + P: ResourcePath, + { + profile_method!(recognize); + + for item in self.routes.iter() { + if item.0.capture_match_info(resource.resource_path()) { + return Some((&item.1, ResourceId(item.0.id()))); + } + } + + None + } + + pub fn recognize_mut(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> + where + R: Resource

, + P: ResourcePath, + { + profile_method!(recognize_mut); + + for item in self.routes.iter_mut() { + if item.0.capture_match_info(resource.resource_path()) { + return Some((&mut item.1, ResourceId(item.0.id()))); + } + } + + None + } + + pub fn recognize_fn(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> + where + F: Fn(&R, &Option) -> bool, + R: Resource

, + P: ResourcePath, + { + profile_method!(recognize_checked); + + for item in self.routes.iter() { + if item.0.capture_match_info_fn(resource, &check, &item.2) { + return Some((&item.1, ResourceId(item.0.id()))); + } + } + + None + } + + pub fn recognize_mut_fn( + &mut self, + resource: &mut R, + check: F, + ) -> Option<(&mut T, ResourceId)> + where + F: Fn(&R, &Option) -> bool, + R: Resource

, + P: ResourcePath, + { + profile_method!(recognize_mut_checked); + + for item in self.routes.iter_mut() { + if item.0.capture_match_info_fn(resource, &check, &item.2) { + return Some((&mut item.1, ResourceId(item.0.id()))); + } + } + + None + } +} + +pub struct RouterBuilder { + resources: Vec<(ResourceDef, T, Option)>, +} + +impl RouterBuilder { + /// Register resource for specified path. + pub fn path( + &mut self, + path: P, + resource: T, + ) -> &mut (ResourceDef, T, Option) { + profile_method!(path); + + self.resources + .push((ResourceDef::new(path), resource, None)); + self.resources.last_mut().unwrap() + } + + /// Register resource for specified path prefix. + pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option) { + profile_method!(prefix); + + self.resources + .push((ResourceDef::prefix(prefix), resource, None)); + self.resources.last_mut().unwrap() + } + + /// Register resource for ResourceDef + pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option) { + profile_method!(rdef); + + self.resources.push((rdef, resource, None)); + self.resources.last_mut().unwrap() + } + + /// Finish configuration and create router instance. + pub fn finish(self) -> Router { + Router { + routes: self.resources, + } + } +} + +#[cfg(test)] +mod tests { + use crate::path::Path; + use crate::router::{ResourceId, Router}; + + #[allow(clippy::cognitive_complexity)] + #[test] + fn test_recognizer_1() { + let mut router = Router::::build(); + router.path("/name", 10).0.set_id(0); + router.path("/name/{val}", 11).0.set_id(1); + router.path("/name/{val}/index.html", 12).0.set_id(2); + router.path("/file/{file}.{ext}", 13).0.set_id(3); + router.path("/v{val}/{val2}/index.html", 14).0.set_id(4); + router.path("/v/{tail:.*}", 15).0.set_id(5); + router.path("/test2/{test}.html", 16).0.set_id(6); + router.path("/{test}/index.html", 17).0.set_id(7); + let mut router = router.finish(); + + let mut path = Path::new("/unknown"); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/name"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + assert_eq!(info, ResourceId(0)); + assert!(path.is_empty()); + + let mut path = Path::new("/name/value"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(info, ResourceId(1)); + assert_eq!(path.get("val").unwrap(), "value"); + assert_eq!(&path["val"], "value"); + + let mut path = Path::new("/name/value2/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 12); + assert_eq!(info, ResourceId(2)); + assert_eq!(path.get("val").unwrap(), "value2"); + + let mut path = Path::new("/file/file.gz"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 13); + assert_eq!(info, ResourceId(3)); + assert_eq!(path.get("file").unwrap(), "file"); + assert_eq!(path.get("ext").unwrap(), "gz"); + + let mut path = Path::new("/vtest/ttt/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 14); + assert_eq!(info, ResourceId(4)); + assert_eq!(path.get("val").unwrap(), "test"); + assert_eq!(path.get("val2").unwrap(), "ttt"); + + let mut path = Path::new("/v/blah-blah/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 15); + assert_eq!(info, ResourceId(5)); + assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html"); + + let mut path = Path::new("/test2/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 16); + assert_eq!(info, ResourceId(6)); + assert_eq!(path.get("test").unwrap(), "index"); + + let mut path = Path::new("/bbb/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 17); + assert_eq!(info, ResourceId(7)); + assert_eq!(path.get("test").unwrap(), "bbb"); + } + + #[test] + fn test_recognizer_2() { + let mut router = Router::::build(); + router.path("/index.json", 10); + router.path("/{source}.json", 11); + let mut router = router.finish(); + + let mut path = Path::new("/index.json"); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + let mut path = Path::new("/test.json"); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + } + + #[test] + fn test_recognizer_with_prefix() { + let mut router = Router::::build(); + router.path("/name", 10).0.set_id(0); + router.path("/name/{val}", 11).0.set_id(1); + let mut router = router.finish(); + + let mut path = Path::new("/name"); + path.skip(5); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test/name"); + path.skip(5); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + let mut path = Path::new("/test/name/value"); + path.skip(5); + let (h, id) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(id, ResourceId(1)); + assert_eq!(path.get("val").unwrap(), "value"); + assert_eq!(&path["val"], "value"); + + // same patterns + let mut router = Router::::build(); + router.path("/name", 10); + router.path("/name/{val}", 11); + let mut router = router.finish(); + + let mut path = Path::new("/name"); + path.skip(6); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test2/name"); + path.skip(6); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + let mut path = Path::new("/test2/name-test"); + path.skip(6); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test2/name/ttt"); + path.skip(6); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(&path["val"], "ttt"); + } +} diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs new file mode 100644 index 000000000..e08a7171a --- /dev/null +++ b/actix-router/src/url.rs @@ -0,0 +1,288 @@ +use crate::ResourcePath; + +#[allow(dead_code)] +const GEN_DELIMS: &[u8] = b":/?#[]@"; +#[allow(dead_code)] +const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; +#[allow(dead_code)] +const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; +#[allow(dead_code)] +const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; +#[allow(dead_code)] +const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~"; +const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~ + !$'()*,"; +const QS: &[u8] = b"+&=;b"; + +#[inline] +fn bit_at(array: &[u8], ch: u8) -> bool { + array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 +} + +#[inline] +fn set_bit(array: &mut [u8], ch: u8) { + array[(ch >> 3) as usize] |= 1 << (ch & 7) +} + +thread_local! { + static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); +} + +#[derive(Default, Clone, Debug)] +pub struct Url { + uri: http::Uri, + path: Option, +} + +impl Url { + pub fn new(uri: http::Uri) -> Url { + let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + + Url { uri, path } + } + + pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { + Url { + path: quoter.requote(uri.path().as_bytes()), + uri, + } + } + + pub fn uri(&self) -> &http::Uri { + &self.uri + } + + pub fn path(&self) -> &str { + if let Some(ref s) = self.path { + s + } else { + self.uri.path() + } + } + + #[inline] + pub fn update(&mut self, uri: &http::Uri) { + self.uri = uri.clone(); + self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + } + + #[inline] + pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) { + self.uri = uri.clone(); + self.path = quoter.requote(uri.path().as_bytes()); + } +} + +impl ResourcePath for Url { + #[inline] + fn path(&self) -> &str { + self.path() + } +} + +pub struct Quoter { + safe_table: [u8; 16], + protected_table: [u8; 16], +} + +impl Quoter { + pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { + let mut q = Quoter { + safe_table: [0; 16], + protected_table: [0; 16], + }; + + // prepare safe table + for i in 0..128 { + if ALLOWED.contains(&i) { + set_bit(&mut q.safe_table, i); + } + if QS.contains(&i) { + set_bit(&mut q.safe_table, i); + } + } + + for ch in safe { + set_bit(&mut q.safe_table, *ch) + } + + // prepare protected table + for ch in protected { + set_bit(&mut q.safe_table, *ch); + set_bit(&mut q.protected_table, *ch); + } + + q + } + + pub fn requote(&self, val: &[u8]) -> Option { + let mut has_pct = 0; + let mut pct = [b'%', 0, 0]; + let mut idx = 0; + let mut cloned: Option> = None; + + let len = val.len(); + while idx < len { + let ch = val[idx]; + + if has_pct != 0 { + pct[has_pct] = val[idx]; + has_pct += 1; + if has_pct == 3 { + has_pct = 0; + let buf = cloned.as_mut().unwrap(); + + if let Some(ch) = restore_ch(pct[1], pct[2]) { + if ch < 128 { + if bit_at(&self.protected_table, ch) { + buf.extend_from_slice(&pct); + idx += 1; + continue; + } + + if bit_at(&self.safe_table, ch) { + buf.push(ch); + idx += 1; + continue; + } + } + buf.push(ch); + } else { + buf.extend_from_slice(&pct[..]); + } + } + } else if ch == b'%' { + has_pct = 1; + if cloned.is_none() { + let mut c = Vec::with_capacity(len); + c.extend_from_slice(&val[..idx]); + cloned = Some(c); + } + } else if let Some(ref mut cloned) = cloned { + cloned.push(ch) + } + idx += 1; + } + + cloned.map(|data| String::from_utf8_lossy(&data).into_owned()) + } +} + +#[inline] +fn from_hex(v: u8) -> Option { + if (b'0'..=b'9').contains(&v) { + Some(v - 0x30) // ord('0') == 0x30 + } else if (b'A'..=b'F').contains(&v) { + Some(v - 0x41 + 10) // ord('A') == 0x41 + } else if (b'a'..=b'f').contains(&v) { + Some(v - 0x61 + 10) // ord('a') == 0x61 + } else { + None + } +} + +#[inline] +fn restore_ch(d1: u8, d2: u8) -> Option { + from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2)) +} + +#[cfg(test)] +mod tests { + use http::Uri; + use std::convert::TryFrom; + + use super::*; + use crate::{Path, ResourceDef}; + + const PROTECTED: &[u8] = b"%/+"; + + fn match_url(pattern: &'static str, url: impl AsRef) -> Path { + let re = ResourceDef::new(pattern); + let uri = Uri::try_from(url.as_ref()).unwrap(); + let mut path = Path::new(Url::new(uri)); + assert!(re.capture_match_info(&mut path)); + path + } + + fn percent_encode(data: &[u8]) -> String { + data.iter().map(|c| format!("%{:02X}", c)).collect() + } + + #[test] + fn test_parse_url() { + let re = "/user/{id}/test"; + + let path = match_url(re, "/user/2345/test"); + assert_eq!(path.get("id").unwrap(), "2345"); + + // "%25" should never be decoded into '%' to guarantee the output is a valid + // percent-encoded format + let path = match_url(re, "/user/qwe%25/test"); + assert_eq!(path.get("id").unwrap(), "qwe%25"); + + let path = match_url(re, "/user/qwe%25rty/test"); + assert_eq!(path.get("id").unwrap(), "qwe%25rty"); + } + + #[test] + fn test_protected_chars() { + let encoded = percent_encode(PROTECTED); + let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); + assert_eq!(path.get("id").unwrap(), &encoded); + } + + #[test] + fn test_non_protecteed_ascii() { + let nonprotected_ascii = ('\u{0}'..='\u{7F}') + .filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8))) + .collect::(); + let encoded = percent_encode(nonprotected_ascii.as_bytes()); + let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); + assert_eq!(path.get("id").unwrap(), &nonprotected_ascii); + } + + #[test] + fn test_valid_utf8_multibyte() { + let test = ('\u{FF00}'..='\u{FFFF}').collect::(); + let encoded = percent_encode(test.as_bytes()); + let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded)); + assert_eq!(path.get("id").unwrap(), &test); + } + + #[test] + fn test_invalid_utf8() { + let invalid_utf8 = percent_encode((0x80..=0xff).collect::>().as_slice()); + let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap(); + let path = Path::new(Url::new(uri)); + + // We should always get a valid utf8 string + assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok()); + } + + #[test] + fn test_from_hex() { + let hex = b"0123456789abcdefABCDEF"; + + for i in 0..256 { + let c = i as u8; + if hex.contains(&c) { + assert!(from_hex(c).is_some()) + } else { + assert!(from_hex(c).is_none()) + } + } + + let expected = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, + ]; + for i in 0..hex.len() { + assert_eq!(from_hex(hex[i]).unwrap(), expected[i]); + } + } +} diff --git a/src/app.rs b/src/app.rs index 5cff20568..da5b45f3a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -334,7 +334,7 @@ where U: AsRef, { let mut rdef = ResourceDef::new(url.as_ref()); - *rdef.name_mut() = name.as_ref().to_string(); + rdef.set_name(name.as_ref()); self.external.push(rdef); self } diff --git a/src/app_service.rs b/src/app_service.rs index 3c1b78474..ce52543b8 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -291,7 +291,7 @@ impl Service for AppRouting { actix_service::always_ready!(); fn call(&self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_checked(&mut req, |req, guards| { + let res = self.router.recognize_fn(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { diff --git a/src/config.rs b/src/config.rs index b072ace16..9e77c0f96 100644 --- a/src/config.rs +++ b/src/config.rs @@ -249,7 +249,7 @@ impl ServiceConfig { U: AsRef, { let mut rdef = ResourceDef::new(url.as_ref()); - *rdef.name_mut() = name.as_ref().to_string(); + rdef.set_name(name.as_ref()); self.external.push(rdef); self } diff --git a/src/dev.rs b/src/dev.rs index b8d95efbb..0817d902f 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -28,11 +28,22 @@ pub use actix_service::{ use crate::http::header::ContentEncoding; use actix_http::{Response, ResponseBuilder}; -pub(crate) fn insert_leading_slash(mut patterns: Vec) -> Vec { - for path in &mut patterns { - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; +use actix_router::Patterns; + +pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { + match &mut patterns { + Patterns::Single(pat) => { + if !pat.is_empty() && !pat.starts_with('/') { + pat.insert(0, '/'); + }; + } + Patterns::List(pats) => { + for pat in pats { + if !pat.is_empty() && !pat.starts_with('/') { + pat.insert(0, '/'); + }; + } + } } patterns diff --git a/src/request.rs b/src/request.rs index 4b950e758..41c8252a8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -509,7 +509,7 @@ mod tests { #[test] fn test_url_for() { let mut res = ResourceDef::new("/user/{name}.{ext}"); - *res.name_mut() = "index".to_string(); + res.set_name("index"); let mut rmap = ResourceMap::new(ResourceDef::new("")); rmap.add(&mut res, None); @@ -539,7 +539,7 @@ mod tests { #[test] fn test_url_for_static() { let mut rdef = ResourceDef::new("/index.html"); - *rdef.name_mut() = "index".to_string(); + rdef.set_name("index"); let mut rmap = ResourceMap::new(ResourceDef::new("")); rmap.add(&mut rdef, None); @@ -560,7 +560,7 @@ mod tests { #[test] fn test_match_name() { let mut rdef = ResourceDef::new("/index.html"); - *rdef.name_mut() = "index".to_string(); + rdef.set_name("index"); let mut rmap = ResourceMap::new(ResourceDef::new("")); rmap.add(&mut rdef, None); @@ -579,7 +579,7 @@ mod tests { fn test_url_for_external() { let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}"); - *rdef.name_mut() = "youtube".to_string(); + rdef.set_name("youtube"); let mut rmap = ResourceMap::new(ResourceDef::new("")); rmap.add(&mut rdef, None); diff --git a/src/resource.rs b/src/resource.rs index 20d1ee17e..851ce0fc9 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -4,7 +4,7 @@ use std::future::Future; use std::rc::Rc; use actix_http::Extensions; -use actix_router::IntoPattern; +use actix_router::{IntoPatterns, Patterns}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, @@ -15,7 +15,7 @@ use futures_util::future::join_all; use crate::{ data::Data, - dev::{insert_leading_slash, AppService, HttpServiceFactory, ResourceDef}, + dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef}, guard::Guard, handler::Handler, responder::Responder, @@ -51,7 +51,7 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// Default behavior could be overridden with `default_resource()` method. pub struct Resource { endpoint: T, - rdef: Vec, + rdef: Patterns, name: Option, routes: Vec, app_data: Option, @@ -61,7 +61,7 @@ pub struct Resource { } impl Resource { - pub fn new(path: T) -> Resource { + pub fn new(path: T) -> Resource { let fref = Rc::new(RefCell::new(None)); Resource { @@ -391,13 +391,13 @@ where }; let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_leading_slash(self.rdef.clone())) + ResourceDef::new(ensure_leading_slash(self.rdef.clone())) } else { ResourceDef::new(self.rdef.clone()) }; if let Some(ref name) = self.name { - *rdef.name_mut() = name.clone(); + rdef.set_name(name); } *self.factory_ref.borrow_mut() = Some(ResourceFactory { diff --git a/src/rmap.rs b/src/rmap.rs index 3c8805d57..0ee4de47e 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -29,9 +29,8 @@ impl ResourceMap { pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option>) { pattern.set_id(self.patterns.len() as u16); self.patterns.push((pattern.clone(), nested)); - if !pattern.name().is_empty() { - self.named - .insert(pattern.name().to_string(), pattern.clone()); + if let Some(name) = pattern.name() { + self.named.insert(name.to_owned(), pattern.clone()); } } @@ -83,10 +82,10 @@ impl ResourceMap { for (pattern, rmap) in &self.patterns { if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); + if let Some(pat_len) = pattern.find_match(path) { + return rmap.has_resource(&path[pat_len..]); } - } else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" { + } else if pattern.is_match(path) || pattern.pattern() == Some("") && path == "/" { return true; } } @@ -100,14 +99,11 @@ impl ResourceMap { for (pattern, rmap) in &self.patterns { if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { + if let Some(plen) = pattern.find_match(path) { return rmap.match_name(&path[plen..]); } } else if pattern.is_match(path) { - return match pattern.name() { - "" => None, - s => Some(s), - }; + return pattern.name(); } } @@ -136,8 +132,9 @@ impl ResourceMap { fn traverse_resource_pattern(&self, remaining: &str) -> String { for (pattern, rmap) in &self.patterns { if let Some(ref rmap) = rmap { - if let Some(prefix_len) = pattern.is_prefix_match(remaining) { - let prefix = pattern.pattern().to_owned(); + if let Some(prefix_len) = pattern.find_match(remaining) { + // TODO: think about unwrap_or + let prefix = pattern.pattern().unwrap_or("").to_owned(); return [ prefix, @@ -146,7 +143,8 @@ impl ResourceMap { .concat(); } } else if pattern.is_match(remaining) { - return pattern.pattern().to_owned(); + // TODO: think about unwrap_or + return pattern.pattern().unwrap_or("").to_owned(); } } @@ -181,10 +179,15 @@ impl ResourceMap { I: AsRef, { if let Some(pattern) = self.named.get(name) { - if pattern.pattern().starts_with('/') { + if pattern + .pattern() + .map(|pat| pat.starts_with('/')) + .unwrap_or(false) + { self.fill_root(path, elements)?; } - if pattern.resource_path(path, elements) { + + if pattern.resource_path_from_iter(path, elements) { Ok(Some(())) } else { Err(UrlGenerationError::NotEnoughElements) @@ -213,7 +216,8 @@ impl ResourceMap { if let Some(ref parent) = self.parent.borrow().upgrade() { parent.fill_root(path, elements)?; } - if self.root.resource_path(path, elements) { + + if self.root.resource_path_from_iter(path, elements) { Ok(()) } else { Err(UrlGenerationError::NotEnoughElements) @@ -233,7 +237,7 @@ impl ResourceMap { if let Some(ref parent) = self.parent.borrow().upgrade() { if let Some(pattern) = parent.named.get(name) { self.fill_root(path, elements)?; - if pattern.resource_path(path, elements) { + if pattern.resource_path_from_iter(path, elements) { Ok(Some(())) } else { Err(UrlGenerationError::NotEnoughElements) @@ -329,7 +333,7 @@ mod tests { let mut root = ResourceMap::new(ResourceDef::root_prefix("")); let mut rdef = ResourceDef::new("/info"); - *rdef.name_mut() = "root_info".to_owned(); + rdef.set_name("root_info"); root.add(&mut rdef, None); let mut user_map = ResourceMap::new(ResourceDef::root_prefix("")); @@ -337,7 +341,7 @@ mod tests { user_map.add(&mut rdef, None); let mut rdef = ResourceDef::new("/post/{post_id}"); - *rdef.name_mut() = "user_post".to_owned(); + rdef.set_name("user_post"); user_map.add(&mut rdef, None); root.add( diff --git a/src/scope.rs b/src/scope.rs index aa546c422..97db53eeb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -530,7 +530,7 @@ impl Service for ScopeService { actix_service::always_ready!(); fn call(&self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_checked(&mut req, |req, guards| { + let res = self.router.recognize_fn(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { diff --git a/src/service.rs b/src/service.rs index 47e7e4acc..148199407 100644 --- a/src/service.rs +++ b/src/service.rs @@ -7,14 +7,14 @@ use actix_http::{ http::{HeaderMap, Method, StatusCode, Uri, Version}, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; -use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; +use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; #[cfg(feature = "cookies")] use cookie::{Cookie, ParseError as CookieParseError}; use crate::{ config::{AppConfig, AppService}, - dev::insert_leading_slash, + dev::ensure_leading_slash, guard::Guard, info::ConnectionInfo, rmap::ResourceMap, @@ -212,14 +212,14 @@ impl ServiceRequest { self.req.match_pattern() } - #[inline] /// Get a mutable reference to the Path parameters. + #[inline] pub fn match_info_mut(&mut self) -> &mut Path { self.req.match_info_mut() } - #[inline] /// Get a reference to a `ResourceMap` of current application. + #[inline] pub fn resource_map(&self) -> &ResourceMap { self.req.resource_map() } @@ -459,14 +459,14 @@ where } pub struct WebService { - rdef: Vec, + rdef: Patterns, name: Option, guards: Vec>, } impl WebService { /// Create new `WebService` instance. - pub fn new(path: T) -> Self { + pub fn new(path: T) -> Self { WebService { rdef: path.patterns(), name: None, @@ -528,7 +528,7 @@ impl WebService { struct WebServiceImpl { srv: T, - rdef: Vec, + rdef: Patterns, name: Option, guards: Vec>, } @@ -551,13 +551,15 @@ where }; let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_leading_slash(self.rdef)) + ResourceDef::new(ensure_leading_slash(self.rdef)) } else { ResourceDef::new(self.rdef) }; + if let Some(ref name) = self.name { - *rdef.name_mut() = name.clone(); + rdef.set_name(name); } + config.register_service(rdef, guards, self.srv, None) } } diff --git a/src/types/path.rs b/src/types/path.rs index f2273a59b..4052646e3 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -209,7 +209,7 @@ mod tests { let resource = ResourceDef::new("/{value}/"); let mut req = TestRequest::with_uri("/32/").to_srv_request(); - resource.match_path(req.match_info_mut()); + resource.capture_match_info(req.match_info_mut()); let (req, mut pl) = req.into_parts(); assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); @@ -221,7 +221,7 @@ mod tests { let resource = ResourceDef::new("/{key}/{value}/"); let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - resource.match_path(req.match_info_mut()); + resource.capture_match_info(req.match_info_mut()); let (req, mut pl) = req.into_parts(); let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl) @@ -247,7 +247,7 @@ mod tests { let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + resource.capture_match_info(req.match_info_mut()); let (req, mut pl) = req.into_parts(); let mut s = Path::::from_request(&req, &mut pl).await.unwrap(); @@ -270,7 +270,7 @@ mod tests { let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + resource.capture_match_info(req.match_info_mut()); let (req, mut pl) = req.into_parts(); let s = Path::::from_request(&req, &mut pl).await.unwrap(); diff --git a/src/web.rs b/src/web.rs index 40ac46275..108ff314f 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,10 +1,10 @@ //! Essentials helper functions and types for application registration. -use actix_http::http::Method; -use actix_router::IntoPattern; use std::future::Future; +use actix_http::http::Method; pub use actix_http::Response as HttpResponse; +use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::error::BlockingError; @@ -51,7 +51,7 @@ pub use crate::types::*; /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) /// ); /// ``` -pub fn resource(path: T) -> Resource { +pub fn resource(path: T) -> Resource { Resource::new(path) } @@ -268,7 +268,7 @@ where /// .finish(my_service) /// ); /// ``` -pub fn service(path: T) -> WebService { +pub fn service(path: T) -> WebService { WebService::new(path) } From e965d8298f421e9c89fe98b1300b8361e948c324 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 12 Aug 2021 20:18:09 +0100 Subject: [PATCH 045/861] HRS security fixes (#2363) --- actix-http/CHANGES.md | 10 + actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 +- actix-http/src/error.rs | 2 +- actix-http/src/h1/chunked.rs | 432 +++++++++++++++++++++++++++++++ actix-http/src/h1/decoder.rs | 481 ++++++++++------------------------- actix-http/src/h1/encoder.rs | 1 + actix-http/src/h1/mod.rs | 2 + 8 files changed, 583 insertions(+), 351 deletions(-) create mode 100644 actix-http/src/h1/chunked.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8ead43718..f52f5ba68 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.8 - 2021-08-09 +### Fixed +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) + + ## 3.0.0-beta.8 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] @@ -210,6 +215,11 @@ [#1878]: https://github.com/actix/actix-web/pull/1878 +## 2.2.1 - 2021-08-09 +### Fixed +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) + + ## 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 a12fed4b9..4ce55dca1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.8" +version = "3.0.0-beta.9" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] diff --git a/actix-http/README.md b/actix-http/README.md index de1ef0a9b..5b06583bc 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.8)](https://docs.rs/actix-http/3.0.0-beta.8) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http/3.0.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.8) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.9) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 54666e072..f7d7f696a 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -196,7 +196,7 @@ pub enum ParseError { #[display(fmt = "IO error: {}", _0)] Io(io::Error), - /// Parsing a field as string failed + /// Parsing a field as string failed. #[display(fmt = "UTF8 error: {}", _0)] Utf8(Utf8Error), } diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs new file mode 100644 index 000000000..1224ce08c --- /dev/null +++ b/actix-http/src/h1/chunked.rs @@ -0,0 +1,432 @@ +use std::{io, task::Poll}; + +use bytes::{Buf as _, Bytes, BytesMut}; + +macro_rules! byte ( + ($rdr:ident) => ({ + if $rdr.len() > 0 { + let b = $rdr[0]; + $rdr.advance(1); + b + } else { + return Poll::Pending + } + }) +); + +#[derive(Debug, PartialEq, Clone)] +pub(super) enum ChunkedState { + Size, + SizeLws, + Extension, + SizeLf, + Body, + BodyCr, + BodyLf, + EndCr, + EndLf, + End, +} + +impl ChunkedState { + pub(super) fn step( + &self, + body: &mut BytesMut, + size: &mut u64, + buf: &mut Option, + ) -> Poll> { + use self::ChunkedState::*; + match *self { + Size => ChunkedState::read_size(body, size), + SizeLws => ChunkedState::read_size_lws(body), + Extension => ChunkedState::read_extension(body), + SizeLf => ChunkedState::read_size_lf(body, size), + Body => ChunkedState::read_body(body, size, buf), + BodyCr => ChunkedState::read_body_cr(body), + BodyLf => ChunkedState::read_body_lf(body), + EndCr => ChunkedState::read_end_cr(body), + EndLf => ChunkedState::read_end_lf(body), + End => Poll::Ready(Ok(ChunkedState::End)), + } + } + + fn read_size( + rdr: &mut BytesMut, + size: &mut u64, + ) -> Poll> { + let radix = 16; + + let rem = match byte!(rdr) { + b @ b'0'..=b'9' => b - b'0', + b @ b'a'..=b'f' => b + 10 - b'a', + b @ b'A'..=b'F' => b + 10 - b'A', + b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), + b';' => return Poll::Ready(Ok(ChunkedState::Extension)), + b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)), + _ => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size line: Invalid Size", + ))); + } + }; + + match size.checked_mul(radix) { + Some(n) => { + *size = n as u64; + *size += rem as u64; + + Poll::Ready(Ok(ChunkedState::Size)) + } + None => { + log::debug!("chunk size would overflow u64"); + Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size line: Size is too big", + ))) + } + } + } + + fn read_size_lws(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + // LWS can follow the chunk size, but no more digits can come + b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)), + b';' => Poll::Ready(Ok(ChunkedState::Extension)), + b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size linear white space", + ))), + } + } + fn read_extension(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), + // strictly 0x20 (space) should be disallowed but we don't parse quoted strings here + 0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid character in chunk extension", + ))), + _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions + } + } + fn read_size_lf( + rdr: &mut BytesMut, + size: &mut u64, + ) -> Poll> { + match byte!(rdr) { + b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), + b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size LF", + ))), + } + } + + fn read_body( + rdr: &mut BytesMut, + rem: &mut u64, + buf: &mut Option, + ) -> Poll> { + log::trace!("Chunked read, remaining={:?}", rem); + + let len = rdr.len() as u64; + if len == 0 { + Poll::Ready(Ok(ChunkedState::Body)) + } else { + let slice; + if *rem > len { + slice = rdr.split().freeze(); + *rem -= len; + } else { + slice = rdr.split_to(*rem as usize).freeze(); + *rem = 0; + } + *buf = Some(slice); + if *rem > 0 { + Poll::Ready(Ok(ChunkedState::Body)) + } else { + Poll::Ready(Ok(ChunkedState::BodyCr)) + } + } + } + + fn read_body_cr(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body CR", + ))), + } + } + fn read_body_lf(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + b'\n' => Poll::Ready(Ok(ChunkedState::Size)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body LF", + ))), + } + } + fn read_end_cr(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end CR", + ))), + } + } + fn read_end_lf(rdr: &mut BytesMut) -> Poll> { + match byte!(rdr) { + b'\n' => Poll::Ready(Ok(ChunkedState::End)), + _ => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end LF", + ))), + } + } +} + +#[cfg(test)] +mod tests { + use actix_codec::Decoder as _; + use bytes::{Bytes, BytesMut}; + use http::Method; + + use crate::{ + error::ParseError, + h1::decoder::{MessageDecoder, PayloadItem}, + HttpMessage as _, Request, + }; + + macro_rules! parse_ready { + ($e:expr) => {{ + match MessageDecoder::::default().decode($e) { + Ok(Some((msg, _))) => msg, + Ok(_) => unreachable!("Eof during parsing http request"), + Err(err) => unreachable!("Error during parsing http request: {:?}", err), + } + }}; + } + + macro_rules! expect_parse_err { + ($e:expr) => {{ + match MessageDecoder::::default().decode($e) { + Err(err) => match err { + ParseError::Io(_) => unreachable!("Parse error expected"), + _ => {} + }, + _ => unreachable!("Error expected"), + } + }}; + } + + #[test] + fn test_parse_chunked_payload_chunk_extension() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(msg.chunked().unwrap()); + + buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"data")); + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"line")); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + } + + #[test] + fn test_request_chunked() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + if let Ok(val) = req.chunked() { + assert!(val); + } else { + unreachable!("Error"); + } + + // intentional typo in "chunked" + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chnked\r\n\r\n", + ); + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_chunked_payload() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = MessageDecoder::::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"data" + ); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"line" + ); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn test_http_request_chunked_payload_and_next_message() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = MessageDecoder::::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(req.chunked().unwrap()); + + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), + ); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert!(req.chunked().unwrap()); + assert_eq!(*req.method(), Method::POST); + assert!(req.chunked().unwrap()); + } + + #[test] + fn test_http_request_chunked_payload_chunks() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\n1111\r\n"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"1111"); + + buf.extend(b"4\r\ndata\r"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + + buf.extend(b"\n4"); + assert!(pl.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r"); + assert!(pl.decode(&mut buf).unwrap().is_none()); + buf.extend(b"\n"); + assert!(pl.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"li"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"li"); + + //trailers + //buf.feed_data("test: test\r\n"); + //not_ready!(reader.parse(&mut buf, &mut readbuf)); + + buf.extend(b"ne\r\n0\r\n"); + let msg = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"ne"); + assert!(pl.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r\n"); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn chunk_extension_quoted() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 2;hello=b;one=\"1 2 3\"\r\n\ + xx", + ); + + let mut reader = MessageDecoder::::default(); + let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + + let chunk = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"xx"))); + } + + #[test] + fn hrs_chunk_extension_invalid() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 2;x\nx\r\n\ + 4c\r\n\ + 0\r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + + let err = pl.decode(&mut buf).unwrap_err(); + assert!(err + .to_string() + .contains("Invalid character in chunk extension")); + } + + #[test] + fn hrs_chunk_size_overflow() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + f0000000000000003\r\n\ + abc\r\n\ + 0\r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + + let err = pl.decode(&mut buf).unwrap_err(); + assert!(err + .to_string() + .contains("Invalid chunk size line: Size is too big")); + } +} diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index f240710c2..313ffd5e0 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1,18 +1,18 @@ -use std::convert::TryFrom; -use std::io; -use std::marker::PhantomData; -use std::task::Poll; +use std::{convert::TryFrom, io, marker::PhantomData, task::Poll}; use actix_codec::Decoder; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use http::header::{HeaderName, HeaderValue}; use http::{header, Method, StatusCode, Uri, Version}; use log::{debug, error, trace}; -use crate::error::ParseError; -use crate::header::HeaderMap; -use crate::message::{ConnectionType, ResponseHead}; -use crate::request::Request; +use super::chunked::ChunkedState; +use crate::{ + error::ParseError, + header::HeaderMap, + message::{ConnectionType, ResponseHead}, + request::Request, +}; pub(crate) const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; @@ -67,6 +67,7 @@ pub(crate) trait MessageType: Sized { let mut has_upgrade_websocket = false; let mut expect = false; let mut chunked = false; + let mut seen_te = false; let mut content_length = None; { @@ -85,8 +86,17 @@ pub(crate) trait MessageType: Sized { }; match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { + header::CONTENT_LENGTH if content_length.is_some() => { + debug!("multiple Content-Length"); + return Err(ParseError::Header); + } + + header::CONTENT_LENGTH => match value.to_str() { + Ok(s) if s.trim().starts_with('+') => { + debug!("illegal Content-Length: {:?}", s); + return Err(ParseError::Header); + } + Ok(s) => { if let Ok(len) = s.parse::() { if len != 0 { content_length = Some(len); @@ -95,15 +105,31 @@ pub(crate) trait MessageType: Sized { debug!("illegal Content-Length: {:?}", s); return Err(ParseError::Header); } - } else { + } + Err(_) => { debug!("illegal Content-Length: {:?}", value); return Err(ParseError::Header); } - } + }, + // transfer-encoding + header::TRANSFER_ENCODING if seen_te => { + debug!("multiple Transfer-Encoding not allowed"); + return Err(ParseError::Header); + } + header::TRANSFER_ENCODING => { + seen_te = true; + if let Ok(s) = value.to_str().map(str::trim) { - chunked = s.eq_ignore_ascii_case("chunked"); + if s.eq_ignore_ascii_case("chunked") { + chunked = true; + } else if s.eq_ignore_ascii_case("identity") { + // allow silently since multiple TE headers are already checked + } else { + debug!("illegal Transfer-Encoding: {:?}", s); + return Err(ParseError::Header); + } } else { return Err(ParseError::Header); } @@ -408,20 +434,6 @@ enum Kind { Eof, } -#[derive(Debug, PartialEq, Clone)] -enum ChunkedState { - Size, - SizeLws, - Extension, - SizeLf, - Body, - BodyCr, - BodyLf, - EndCr, - EndLf, - End, -} - impl Decoder for PayloadDecoder { type Item = PayloadItem; type Error = io::Error; @@ -451,19 +463,23 @@ impl Decoder for PayloadDecoder { Kind::Chunked(ref mut state, ref mut size) => { loop { let mut buf = None; + // advances the chunked state *state = match state.step(src, size, &mut buf) { Poll::Pending => return Ok(None), Poll::Ready(Ok(state)) => state, Poll::Ready(Err(e)) => return Err(e), }; + if *state == ChunkedState::End { trace!("End of chunked stream"); return Ok(Some(PayloadItem::Eof)); } + if let Some(buf) = buf { return Ok(Some(PayloadItem::Chunk(buf))); } + if src.is_empty() { return Ok(None); } @@ -480,201 +496,40 @@ impl Decoder for PayloadDecoder { } } -macro_rules! byte ( - ($rdr:ident) => ({ - if $rdr.len() > 0 { - let b = $rdr[0]; - $rdr.advance(1); - b - } else { - return Poll::Pending - } - }) -); - -impl ChunkedState { - fn step( - &self, - body: &mut BytesMut, - size: &mut u64, - buf: &mut Option, - ) -> Poll> { - use self::ChunkedState::*; - match *self { - Size => ChunkedState::read_size(body, size), - SizeLws => ChunkedState::read_size_lws(body), - Extension => ChunkedState::read_extension(body), - SizeLf => ChunkedState::read_size_lf(body, size), - Body => ChunkedState::read_body(body, size, buf), - BodyCr => ChunkedState::read_body_cr(body), - BodyLf => ChunkedState::read_body_lf(body), - EndCr => ChunkedState::read_end_cr(body), - EndLf => ChunkedState::read_end_lf(body), - End => Poll::Ready(Ok(ChunkedState::End)), - } - } - - fn read_size( - rdr: &mut BytesMut, - size: &mut u64, - ) -> Poll> { - let radix = 16; - match byte!(rdr) { - b @ b'0'..=b'9' => { - *size *= radix; - *size += u64::from(b - b'0'); - } - b @ b'a'..=b'f' => { - *size *= radix; - *size += u64::from(b + 10 - b'a'); - } - b @ b'A'..=b'F' => { - *size *= radix; - *size += u64::from(b + 10 - b'A'); - } - b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), - b';' => return Poll::Ready(Ok(ChunkedState::Extension)), - b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size line: Invalid Size", - ))); - } - } - Poll::Ready(Ok(ChunkedState::Size)) - } - - fn read_size_lws(rdr: &mut BytesMut) -> Poll> { - trace!("read_size_lws"); - match byte!(rdr) { - // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)), - b';' => Poll::Ready(Ok(ChunkedState::Extension)), - b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size linear white space", - ))), - } - } - fn read_extension(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions - } - } - fn read_size_lf( - rdr: &mut BytesMut, - size: &mut u64, - ) -> Poll> { - match byte!(rdr) { - b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), - b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size LF", - ))), - } - } - - fn read_body( - rdr: &mut BytesMut, - rem: &mut u64, - buf: &mut Option, - ) -> Poll> { - trace!("Chunked read, remaining={:?}", rem); - - let len = rdr.len() as u64; - if len == 0 { - Poll::Ready(Ok(ChunkedState::Body)) - } else { - let slice; - if *rem > len { - slice = rdr.split().freeze(); - *rem -= len; - } else { - slice = rdr.split_to(*rem as usize).freeze(); - *rem = 0; - } - *buf = Some(slice); - if *rem > 0 { - Poll::Ready(Ok(ChunkedState::Body)) - } else { - Poll::Ready(Ok(ChunkedState::BodyCr)) - } - } - } - - fn read_body_cr(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body CR", - ))), - } - } - fn read_body_lf(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\n' => Poll::Ready(Ok(ChunkedState::Size)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body LF", - ))), - } - } - fn read_end_cr(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end CR", - ))), - } - } - fn read_end_lf(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\n' => Poll::Ready(Ok(ChunkedState::End)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end LF", - ))), - } - } -} - #[cfg(test)] mod tests { use bytes::{Bytes, BytesMut}; use http::{Method, Version}; use super::*; - use crate::error::ParseError; - use crate::http::header::{HeaderName, SET_COOKIE}; - use crate::HttpMessage; + use crate::{ + error::ParseError, + http::header::{HeaderName, SET_COOKIE}, + HttpMessage as _, + }; impl PayloadType { - fn unwrap(self) -> PayloadDecoder { + pub(crate) fn unwrap(self) -> PayloadDecoder { match self { PayloadType::Payload(pl) => pl, _ => panic!(), } } - fn is_unhandled(&self) -> bool { + pub(crate) fn is_unhandled(&self) -> bool { matches!(self, PayloadType::Stream(_)) } } impl PayloadItem { - fn chunk(self) -> Bytes { + pub(crate) fn chunk(self) -> Bytes { match self { PayloadItem::Chunk(chunk) => chunk, _ => panic!("error"), } } - fn eof(&self) -> bool { + + pub(crate) fn eof(&self) -> bool { matches!(*self, PayloadItem::Eof) } } @@ -967,34 +822,6 @@ mod tests { assert!(req.upgrade()); } - #[test] - fn test_request_chunked() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(val); - } else { - unreachable!("Error"); - } - - // intentional typo in "chunked" - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(!val); - } else { - unreachable!("Error"); - } - } - #[test] fn test_headers_content_length_err_1() { let mut buf = BytesMut::from( @@ -1112,126 +939,6 @@ mod tests { expect_parse_err!(&mut buf); } - #[test] - fn test_http_request_chunked_payload() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"data" - ); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"line" - ); - assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert!(req.chunked().unwrap()); - assert_eq!(*req.method(), Method::POST); - assert!(req.chunked().unwrap()); - } - - #[test] - fn test_http_request_chunked_payload_chunks() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\n1111\r\n"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"1111"); - - buf.extend(b"4\r\ndata\r"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - buf.extend(b"\n4"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - buf.extend(b"\n"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"li"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"li"); - - //trailers - //buf.feed_data("test: test\r\n"); - //not_ready!(reader.parse(&mut buf, &mut readbuf)); - - buf.extend(b"ne\r\n0\r\n"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_parse_chunked_payload_chunk_extension() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\ - \r\n", - ); - - let mut reader = MessageDecoder::::default(); - let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(msg.chunked().unwrap()); - - buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - } - #[test] fn test_response_http10_read_until_eof() { let mut buf = BytesMut::from("HTTP/1.0 200 Ok\r\n\r\ntest data"); @@ -1243,4 +950,84 @@ mod tests { let chunk = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"test data"))); } + + #[test] + fn hrs_multiple_content_length() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: 4\r\n\ + Content-Length: 2\r\n\ + \r\n\ + abcd", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn hrs_content_length_plus() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: +3\r\n\ + \r\n\ + 000", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn hrs_unknown_transfer_encoding() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Transfer-Encoding: JUNK\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 5\r\n\ + hello\r\n\ + 0", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn hrs_multiple_transfer_encoding() { + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: 51\r\n\ + Transfer-Encoding: identity\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 0\r\n\ + \r\n\ + GET /forbidden HTTP/1.1\r\n\ + Host: example.com\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn transfer_encoding_agrees() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: 3\r\n\ + Transfer-Encoding: identity\r\n\ + \r\n\ + 0\r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + + let chunk = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"0\r\n"))); + } } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 254981123..4e5c9d238 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -81,6 +81,7 @@ pub(crate) trait MessageType: Sized { match length { BodySize::Stream => { if chunked { + skip_len = true; if camel_case { dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") } else { diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 7e6df6ceb..17cbfb90f 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -1,6 +1,8 @@ //! HTTP/1 protocol implementation. + use bytes::{Bytes, BytesMut}; +mod chunked; mod client; mod codec; mod decoder; From 384164cc148e4bf31a8ff3ddffd1139e64a1c15f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 6 Aug 2021 20:10:58 +0100 Subject: [PATCH 046/861] update graphs --- docs/graphs/net-only.dot | 3 +-- docs/graphs/web-focus.dot | 3 ++- docs/graphs/web-only.dot | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index bee0185ab..8a58ec2b8 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -4,7 +4,7 @@ digraph { subgraph cluster_net { label="actix-net" "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" - "actix-tls" "actix-tracing" "actix-utils" "actix-router" + "actix-tls" "actix-tracing" "actix-utils" } subgraph cluster_other { @@ -25,7 +25,6 @@ digraph { "actix-tls" -> { "tokio-util" }[color="#009900"] "actix-server" -> { "actix-service" "actix-rt" "actix-utils" "tokio" } "actix-rt" -> { "actix-macros" "tokio" } - "actix-router" -> { "bytestring" } "local-channel" -> { "local-waker" } diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 2c6e2779b..63b3eaa82 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -10,6 +10,7 @@ digraph { "web-actors" "web-codegen" "http-test" + "router" { rank=same; "multipart" "web-actors" "http-test" }; { rank=same; "files" "awc" "web" }; @@ -36,7 +37,7 @@ digraph { "rt" -> { "macros" } { rank=same; "utils" "codec" }; - { rank=same; "rt" "macros" "service" "router" }; + { rank=same; "rt" "macros" "service" }; // actix diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index b0decd818..ee74c292b 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -10,9 +10,10 @@ digraph { "actix-web-codegen" "actix-http-test" "actix-test" + "actix-router" } - "actix-web" -> { "actix-web-codegen" "actix-http" } + "actix-web" -> { "actix-web-codegen" "actix-http" "actix-router" } "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } From a0c0bff944febe1d984aedc4866acee1bed95bdd Mon Sep 17 00:00:00 2001 From: Thales <46510852+thalesfragoso@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:41:19 -0300 Subject: [PATCH 047/861] Don't create a slice to potential uninit data on h1 encoder (#2364) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 4 ++++ actix-http/benches/write-camel-case.rs | 10 +++++++--- actix-http/src/h1/encoder.rs | 15 +++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f52f5ba68..9ed28105f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased - 2021-xx-xx +### Fixed +* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] + +[#2364]: https://github.com/actix/actix-web/pull/2364 ## 3.0.0-beta.8 - 2021-08-09 ### Fixed diff --git a/actix-http/benches/write-camel-case.rs b/actix-http/benches/write-camel-case.rs index fa4930eb9..ccf09b37e 100644 --- a/actix-http/benches/write-camel-case.rs +++ b/actix-http/benches/write-camel-case.rs @@ -18,7 +18,8 @@ fn bench_write_camel_case(c: &mut Criterion) { group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| { b.iter(|| { let mut buf = black_box([0; 24]); - _new::write_camel_case(black_box(bts), &mut buf) + let len = black_box(bts.len()); + _new::write_camel_case(black_box(bts), buf.as_mut_ptr(), len) }); }); } @@ -30,9 +31,12 @@ criterion_group!(benches, bench_write_camel_case); criterion_main!(benches); mod _new { - pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { + pub fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) { // first copy entire (potentially wrong) slice to output - buffer[..value.len()].copy_from_slice(value); + let buffer = unsafe { + std::ptr::copy_nonoverlapping(value.as_ptr(), buf, len); + std::slice::from_raw_parts_mut(buf, len) + }; let mut iter = value.iter(); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 4e5c9d238..5e1d47785 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -175,7 +175,7 @@ pub(crate) trait MessageType: Sized { unsafe { if camel_case { // use Camel-Case headers - write_camel_case(k, from_raw_parts_mut(buf, k_len)); + write_camel_case(k, buf, k_len); } else { write_data(k, buf, k_len); } @@ -473,15 +473,22 @@ impl TransferEncoding { } /// # Safety -/// Callers must ensure that the given length matches given value length. +/// Callers must ensure that the given `len` matches the given `value` length and that `buf` is +/// valid for writes of at least `len` bytes. unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { debug_assert_eq!(value.len(), len); copy_nonoverlapping(value.as_ptr(), buf, len); } -fn write_camel_case(value: &[u8], buffer: &mut [u8]) { +/// # Safety +/// Callers must ensure that the given `len` matches the given `value` length and that `buf` is +/// valid for writes of at least `len` bytes. +unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) { // first copy entire (potentially wrong) slice to output - buffer[..value.len()].copy_from_slice(value); + write_data(value, buf, len); + + // SAFETY: We just initialized the buffer with `value` + let buffer = from_raw_parts_mut(buf, len); let mut iter = value.iter(); From 5f412c67db4c65dba51942bd098b58acc8fae035 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 13 Aug 2021 18:49:58 +0100 Subject: [PATCH 048/861] clippy --- actix-files/src/error.rs | 1 + actix-http/src/header/map.rs | 2 +- actix-http/src/lib.rs | 2 +- actix-http/src/message.rs | 4 ++-- actix-router/src/path.rs | 6 +++--- actix-router/src/resource.rs | 6 +++--- src/http/header/content_disposition.rs | 2 +- src/middleware/logger.rs | 2 +- src/request.rs | 4 ++-- src/service.rs | 2 +- src/types/either.rs | 4 ++-- src/types/json.rs | 2 +- 12 files changed, 19 insertions(+), 18 deletions(-) diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index e5f2d4779..f8e32eef7 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -21,6 +21,7 @@ impl ResponseError for FilesError { } } +#[allow(clippy::enum_variant_names)] #[derive(Display, Debug, PartialEq)] pub enum UriSegmentError { /// The segment started with the wrapped invalid character. diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 634d9282f..a8fd9715b 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -684,7 +684,7 @@ impl<'a> Iterator for Iter<'a> { fn next(&mut self) -> Option { // handle in-progress multi value lists first - if let Some((ref name, ref mut vals)) = self.multi_inner { + if let Some((name, ref mut vals)) = self.multi_inner { match vals.get(self.multi_idx) { Some(val) => { self.multi_idx += 1; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index d22e1ee44..17ee3ff29 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -14,7 +14,7 @@ //! [rustls]: https://crates.io/crates/rustls //! [trust-dns]: https://crates.io/crates/trust-dns -#![deny(rust_2018_idioms, nonstandard_style)] +#![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)] #![allow( clippy::type_complexity, clippy::too_many_arguments, diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index e85d686b7..84125fb3a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -209,7 +209,7 @@ impl RequestHeadType { impl AsRef for RequestHeadType { fn as_ref(&self) -> &RequestHead { match self { - RequestHeadType::Owned(head) => &head, + RequestHeadType::Owned(head) => head, RequestHeadType::Rc(head, _) => head.as_ref(), } } @@ -363,7 +363,7 @@ impl std::ops::Deref for Message { type Target = T; fn deref(&self) -> &Self::Target { - &self.head.as_ref() + self.head.as_ref() } } diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index e29591f96..9af7b0b8b 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -125,7 +125,7 @@ impl Path { for (seg_name, val) in self.segments.iter() { if name == seg_name { return match val { - PathItem::Static(ref s) => Some(&s), + PathItem::Static(ref s) => Some(s), PathItem::Segment(s, e) => { Some(&self.path.path()[(*s as usize)..(*e as usize)]) } @@ -183,7 +183,7 @@ impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> { if self.idx < self.params.segment_count() { let idx = self.idx; let res = match self.params.segments[idx].1 { - PathItem::Static(ref s) => &s, + PathItem::Static(ref s) => s, PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)], }; self.idx += 1; @@ -207,7 +207,7 @@ impl Index for Path { fn index(&self, idx: usize) -> &str { match self.segments[idx].1 { - PathItem::Static(ref s) => &s, + PathItem::Static(ref s) => s, PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)], } } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 61ff587a5..69e10b2bd 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -276,7 +276,7 @@ impl ResourceDef { let mut pattern_data = Vec::new(); for pattern in &patterns { - match ResourceDef::parse(&pattern, false, true) { + match ResourceDef::parse(pattern, false, true) { (PatternType::Dynamic(re, names), _) => { re_set.push(re.as_str().to_owned()); pattern_data.push((re, names)); @@ -790,7 +790,7 @@ impl ResourceDef { profile_section!(pattern_dynamic_extract_captures); for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { + if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { log::error!( @@ -820,7 +820,7 @@ impl ResourceDef { }; for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { + if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { log::error!("Dynamic path match but not all segments found: {}", name); diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 9f67baffb..6e75fde92 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -457,7 +457,7 @@ impl Header for ContentDisposition { fn parse(msg: &T) -> Result { if let Some(h) = msg.headers().get(&Self::name()) { - Self::from_raw(&h) + Self::from_raw(h) } else { Err(crate::error::ParseError::Header) } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index bbb0e3dc4..0f09b6ad6 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -553,7 +553,7 @@ impl FormatText { *self = FormatText::Str(s.to_string()); } FormatText::RemoteAddr => { - let s = if let Some(ref peer) = req.connection_info().remote_addr() { + let s = if let Some(peer) = req.connection_info().remote_addr() { FormatText::Str((*peer).to_string()) } else { FormatText::Str("-".to_string()) diff --git a/src/request.rs b/src/request.rs index 41c8252a8..59850b4ca 100644 --- a/src/request.rs +++ b/src/request.rs @@ -184,7 +184,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.resource_map().url_for(&self, name, elements) + self.resource_map().url_for(self, name, elements) } /// Generate url for named resource @@ -199,7 +199,7 @@ impl HttpRequest { #[inline] /// Get a reference to a `ResourceMap` of current application. pub fn resource_map(&self) -> &ResourceMap { - &self.app_state().rmap() + self.app_state().rmap() } /// Peer socket address. diff --git a/src/service.rs b/src/service.rs index 148199407..48167e5b3 100644 --- a/src/service.rs +++ b/src/service.rs @@ -117,7 +117,7 @@ impl ServiceRequest { /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.req.head() + self.req.head() } /// This method returns reference to the request head diff --git a/src/types/either.rs b/src/types/either.rs index d3b003587..35e63cec9 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -253,7 +253,7 @@ where Ok(bytes) => { let fallback = bytes.clone(); let left = - L::from_request(&this.req, &mut payload_from_bytes(bytes)); + L::from_request(this.req, &mut payload_from_bytes(bytes)); EitherExtractState::Left { left, fallback } } Err(err) => break Err(EitherExtractError::Bytes(err)), @@ -265,7 +265,7 @@ where Ok(extracted) => break Ok(Either::Left(extracted)), Err(left_err) => { let right = R::from_request( - &this.req, + this.req, &mut payload_from_bytes(mem::take(fallback)), ); EitherExtractState::Right { diff --git a/src/types/json.rs b/src/types/json.rs index fc02c8854..ab9708c53 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -425,7 +425,7 @@ where } } None => { - let json = serde_json::from_slice::(&buf) + let json = serde_json::from_slice::(buf) .map_err(JsonPayloadError::Deserialize)?; return Poll::Ready(Ok(json)); } From ff07816b650997b0050811c1fd300c0da1104b59 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 29 Aug 2021 08:42:22 +0800 Subject: [PATCH 049/861] update httparse for uninit header parsing (#2374) --- actix-http/Cargo.toml | 2 +- actix-http/src/h1/decoder.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4ce55dca1..68f980982 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,7 +59,7 @@ futures-core = { version = "0.3.7", default-features = false, features = ["alloc futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.1" http = "0.2.2" -httparse = "1.3" +httparse = "1.5.1" itoa = "0.4" language-tags = "0.3" local-channel = "0.1" diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 313ffd5e0..91a3af44f 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, io, marker::PhantomData, task::Poll}; +use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Poll}; use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; @@ -212,10 +212,17 @@ impl MessageType for Request { let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY; let (len, method, uri, ver, h_len) = { - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY; + // SAFETY: + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is + // safe because the type we are claiming to have initialized here is a + // bunch of `MaybeUninit`s, which do not require initialization. + let mut parsed = unsafe { + MaybeUninit::<[MaybeUninit>; MAX_HEADERS]>::uninit() + .assume_init() + }; - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src)? { + let mut req = httparse::Request::new(&mut []); + match req.parse_with_uninit_headers(src, &mut parsed)? { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; From f9da6e48e0aef496001528daa68298ff9107a895 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 30 Aug 2021 22:05:49 +0300 Subject: [PATCH 050/861] ResourceDef: define behavior for prefix with trailing slash (#2355) * ResourceDef: define behavior * fix tests * add scope test * revert firestorm bump * update changelog * fmt Co-authored-by: Rob Ede --- actix-files/src/files.rs | 2 +- actix-router/CHANGES.md | 3 + actix-router/src/resource.rs | 169 +++++++++++++++++++---------------- src/scope.rs | 66 ++++++++++++++ 4 files changed, 163 insertions(+), 77 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 49d81eb03..68879822a 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -106,7 +106,7 @@ impl Files { }; Files { - path: mount_path.to_owned(), + path: mount_path.trim_end_matches('/').to_owned(), directory: dir, index: None, show_index: false, diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index dea7cb76f..140d108e2 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -5,11 +5,14 @@ * Disallow prefix routes with tail segments. [#379] * Enforce path separators on dynamic prefixes. [#378] * Improve malformed path error message. [#384] +* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +* Prefix segments with trailing slashes define a trailing empty segment. [#2355] [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 [#380]: https://github.com/actix/actix-net/pull/380 [#384]: https://github.com/actix/actix-net/pull/384 +[#2355]: https://github.com/actix/actix-web/pull/2355 ## 0.5.0-beta.1 - 2021-07-20 diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 69e10b2bd..fbf29cc7a 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -28,9 +28,27 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// regex engine. /// /// +/// # Pattern Format and Matching Behavior +/// +/// Resource pattern is defined as a string of zero or more _segments_ where each segment is +/// preceeded by a slash `/`. +/// +/// This means that pattern string __must__ either be empty or begin with a slash (`/`). +/// This also implies that a trailing slash in pattern defines an empty segment. +/// For example, the pattern `"/user/"` has two segments: `["user", ""]` +/// +/// A key point to undertand is that `ResourceDef` matches segments, not strings. +/// It matches segments individually. +/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`, +/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`. +/// +/// This definition is consistent with the definition of absolute URL path in +/// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) +/// +/// /// # Static Resources -/// A static resource is the most basic type of definition. Pass a regular string to -/// [new][Self::new]. Conforming paths must match the string exactly. +/// A static resource is the most basic type of definition. Pass a pattern to +/// [new][Self::new]. Conforming paths must match the pattern exactly. /// /// ## Examples /// ``` @@ -39,6 +57,7 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// /// assert!(resource.is_match("/home")); /// +/// assert!(!resource.is_match("/home/")); /// assert!(!resource.is_match("/home/new")); /// assert!(!resource.is_match("/homes")); /// assert!(!resource.is_match("/search")); @@ -85,12 +104,13 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// /// /// # Prefix Resources -/// A prefix resource is defined as pattern that can match just the start of a path. +/// A prefix resource is defined as pattern that can match just the start of a path, up to a +/// segment boundary. /// -/// This library chooses to restrict that definition slightly. In particular, when matching, the -/// prefix must be separated from the remaining part of the path by a `/` character, either at the -/// end of the prefix pattern or at the start of the the remaining slice. In practice, this is not -/// much of a limitation. +/// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior. +/// They define and therefore require an empty segment in order to match. Examples are given below. +/// +/// Empty pattern matches any path as a prefix. /// /// Prefix resources can contain dynamic segments. /// @@ -102,9 +122,12 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(resource.is_match("/home/new")); /// assert!(!resource.is_match("/homes")); /// +/// // prefix pattern with a trailing slash /// let resource = ResourceDef::prefix("/user/{id}/"); /// assert!(resource.is_match("/user/123/")); -/// assert!(resource.is_match("/user/123/stars")); +/// assert!(resource.is_match("/user/123//stars")); +/// assert!(!resource.is_match("/user/123/stars")); +/// assert!(!resource.is_match("/user/123")); /// ``` /// /// @@ -117,6 +140,10 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// `{name:regex}`. For example, `/user/{id:\d+}` will only match paths where the user ID /// is numeric. /// +/// The regex could potentially match multiple segments. If this is not wanted, then care must be +/// taken to avoid matching a slash `/`. It is guaranteed, however, that the match ends at a +/// segment boundary; the pattern `r"(/|$)` is always appended to the regex. +/// /// By default, dynamic segments use this regex: `[^/]+`. This shows why it is the case, as shown in /// the earlier section, that segments capture a slice of the path up to the next `/` character. /// @@ -298,7 +325,7 @@ impl ResourceDef { } } - /// Constructs a new resource definition using a string pattern that performs prefix matching. + /// Constructs a new resource definition using a pattern that performs prefix matching. /// /// More specifically, the regular expressions generated for matching are different when using /// this method vs using `new`; they will not be appended with the `$` meta-character that @@ -320,13 +347,6 @@ impl ResourceDef { /// assert!(!resource.is_match("user/123")); /// assert!(!resource.is_match("user/123/stars")); /// assert!(!resource.is_match("/foo")); - /// - /// let resource = ResourceDef::prefix("user/{id}"); - /// assert!(resource.is_match("user/123")); - /// assert!(resource.is_match("user/123/stars")); - /// assert!(!resource.is_match("/user/123")); - /// assert!(!resource.is_match("/user/123/stars")); - /// assert!(!resource.is_match("foo")); /// ``` pub fn prefix(path: &str) -> Self { profile_method!(prefix); @@ -591,24 +611,7 @@ impl ResourceDef { match self.pat_type { PatternType::Static(ref s) => s == path, - - PatternType::Prefix(ref prefix) if prefix == path => true, - PatternType::Prefix(ref prefix) => is_strict_prefix(prefix, path), - - // dynamic prefix - PatternType::Dynamic(ref re, _) if !re.as_str().ends_with('$') => { - match re.find(path) { - // prefix matches exactly - Some(m) if m.end() == path.len() => true, - - // prefix matches part - Some(m) => is_strict_prefix(m.as_str(), path), - - // prefix does not match - None => false, - } - } - + PatternType::Prefix(ref prefix) => is_prefix(prefix, path), PatternType::Dynamic(ref re, _) => re.is_match(path), PatternType::DynamicSet(ref re, _) => re.is_match(path), } @@ -656,30 +659,15 @@ impl ResourceDef { PatternType::Static(segment) if path == segment => Some(segment.len()), PatternType::Static(_) => None, - PatternType::Prefix(prefix) if path == prefix => Some(prefix.len()), - PatternType::Prefix(prefix) if is_strict_prefix(prefix, path) => Some(prefix.len()), + PatternType::Prefix(prefix) if is_prefix(prefix, path) => Some(prefix.len()), PatternType::Prefix(_) => None, - // dynamic prefix - PatternType::Dynamic(ref re, _) if !re.as_str().ends_with('$') => { - match re.find(path) { - // prefix matches exactly - Some(m) if m.end() == path.len() => Some(m.end()), - - // prefix matches part - Some(m) if is_strict_prefix(m.as_str(), path) => Some(m.end()), - - // prefix does not match - _ => None, - } - } - - PatternType::Dynamic(re, _) => re.find(path).map(|m| m.end()), + PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()), PatternType::DynamicSet(re, params) => { let idx = re.matches(path).into_iter().next()?; let (ref pattern, _) = params[idx]; - pattern.find(path).map(|m| m.end()) + Some(pattern.captures(path)?[1].len()) } } } @@ -802,7 +790,7 @@ impl ResourceDef { } }; - (captures[0].len(), Some(names)) + (captures[1].len(), Some(names)) } PatternType::DynamicSet(re, params) => { @@ -828,7 +816,7 @@ impl ResourceDef { } } - (captures[0].len(), Some(names)) + (captures[1].len(), Some(names)) } }; @@ -1112,8 +1100,16 @@ impl ResourceDef { ); } - if !is_prefix && !has_tail_segment { - re.push('$'); + // Store the pattern in capture group #1 to have context info outside it + let mut re = format!("({})", re); + + // Ensure the match ends at a segment boundary + if !has_tail_segment { + if is_prefix { + re.push_str(r"(/|$)"); + } else { + re.push('$'); + } } let re = match Regex::new(&re) { @@ -1185,10 +1181,12 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { } /// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. -/// -/// The `strict` refers to the fact that this will return `false` if `prefix == path`. -fn is_strict_prefix(prefix: &str, path: &str) -> bool { - path.starts_with(prefix) && (prefix.ends_with('/') || path[prefix.len()..].starts_with('/')) +fn is_prefix(prefix: &str, path: &str) -> bool { + match path.strip_prefix(prefix) { + // Ensure the match ends at segment boundary + Some(rem) if rem.is_empty() || rem.starts_with('/') => true, + _ => false, + } } #[cfg(test)] @@ -1501,54 +1499,70 @@ mod tests { let re = ResourceDef::prefix("/name/"); assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); + assert!(re.is_match("/name//gs")); + assert!(!re.is_match("/name/gs")); assert!(!re.is_match("/name")); let mut path = Path::new("/name/gs"); + assert!(!re.capture_match_info(&mut path)); + + let mut path = Path::new("/name//gs"); assert!(re.capture_match_info(&mut path)); - assert_eq!(path.unprocessed(), "gs"); + assert_eq!(path.unprocessed(), "/gs"); let re = ResourceDef::root_prefix("name/"); assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); + assert!(re.is_match("/name//gs")); + assert!(!re.is_match("/name/gs")); assert!(!re.is_match("/name")); let mut path = Path::new("/name/gs"); - assert!(re.capture_match_info(&mut path)); - assert_eq!(path.unprocessed(), "gs"); + assert!(!re.capture_match_info(&mut path)); } #[test] fn prefix_dynamic() { - let re = ResourceDef::prefix("/{name}/"); + let re = ResourceDef::prefix("/{name}"); assert!(re.is_prefix()); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); + assert!(re.is_match("/name")); - assert_eq!(re.find_match("/name/"), Some(6)); - assert_eq!(re.find_match("/name/gs"), Some(6)); - assert_eq!(re.find_match("/name"), None); + assert_eq!(re.find_match("/name/"), Some(5)); + assert_eq!(re.find_match("/name/gs"), Some(5)); + assert_eq!(re.find_match("/name"), Some(5)); + assert_eq!(re.find_match(""), None); let mut path = Path::new("/test2/"); assert!(re.capture_match_info(&mut path)); assert_eq!(&path["name"], "test2"); assert_eq!(&path[0], "test2"); - assert_eq!(path.unprocessed(), ""); + assert_eq!(path.unprocessed(), "/"); let mut path = Path::new("/test2/subpath1/subpath2/index.html"); assert!(re.capture_match_info(&mut path)); assert_eq!(&path["name"], "test2"); assert_eq!(&path[0], "test2"); - assert_eq!(path.unprocessed(), "subpath1/subpath2/index.html"); + assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html"); let resource = ResourceDef::prefix("/user"); // input string shorter than prefix assert!(resource.find_match("/foo").is_none()); } + #[test] + fn prefix_empty() { + let re = ResourceDef::prefix(""); + + assert!(re.is_prefix()); + + assert!(re.is_match("")); + assert!(re.is_match("/")); + assert!(re.is_match("/name/test/test")); + } + #[test] fn build_path_list() { let mut s = String::new(); @@ -1667,14 +1681,17 @@ mod tests { } #[test] - fn consistent_match_length() { - let result = Some(5); + fn prefix_trailing_slash() { + // The prefix "/abc/" matches two segments: ["user", ""] + // These are not prefixes let re = ResourceDef::prefix("/abc/"); - assert_eq!(re.find_match("/abc/def"), result); + assert_eq!(re.find_match("/abc/def"), None); + assert_eq!(re.find_match("/abc//def"), Some(5)); let re = ResourceDef::prefix("/{id}/"); - assert_eq!(re.find_match("/abc/def"), result); + assert_eq!(re.find_match("/abc/def"), None); + assert_eq!(re.find_match("/abc//def"), Some(5)); } #[test] diff --git a/src/scope.rs b/src/scope.rs index 97db53eeb..b2edaedab 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1153,4 +1153,70 @@ mod tests { Bytes::from_static(b"http://localhost:8080/a/b/c/12345") ); } + + #[actix_rt::test] + async fn dynamic_scopes() { + let srv = init_service( + App::new().service( + web::scope("/{a}/").service( + web::scope("/{b}/") + .route("", web::get().to(|_: HttpRequest| HttpResponse::Created())) + .route( + "/", + web::get().to(|_: HttpRequest| HttpResponse::Accepted()), + ) + .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())), + ), + ), + ) + .await; + + // note the unintuitive behavior with trailing slashes on scopes with dynamic segments + let req = TestRequest::with_uri("/a//b//c").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/a//b/").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/a//b//").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::ACCEPTED); + + let req = TestRequest::with_uri("/a//b//c/d").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let srv = init_service( + App::new().service( + web::scope("/{a}").service( + web::scope("/{b}") + .route("", web::get().to(|_: HttpRequest| HttpResponse::Created())) + .route( + "/", + web::get().to(|_: HttpRequest| HttpResponse::Accepted()), + ) + .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/a/b/c").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/a/b").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/a/b/").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::ACCEPTED); + + let req = TestRequest::with_uri("/a/b/c/d").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } } From 4bb32fb19b5fb4105e7ca2b7371557d0d21b0346 Mon Sep 17 00:00:00 2001 From: Sam De Roeck <31270289+sadroeck@users.noreply.github.com> Date: Mon, 30 Aug 2021 21:07:12 +0200 Subject: [PATCH 051/861] [fix] Bump actix-http dependency to 3.0.0-beta.9, up from 3.0.0-beta.8 (#2360) Fixes https://rustsec.org/advisories/RUSTSEC-2021-0081 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ff3321f47..f2ce46ee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.8" +actix-http = "3.0.0-beta.9" ahash = "0.7" bytes = "1" From 168b2f227d1959252dc47518641da7d214a81ed3 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 31 Aug 2021 02:20:40 +0530 Subject: [PATCH 052/861] compile time validation of path (#2350) * compile time validation of path * added trybuild err message * Update Cargo.toml * add changelog entry * test more cases of path validation * fmt Co-authored-by: Rob Ede --- actix-router/src/resource.rs | 5 ++- actix-web-codegen/CHANGES.md | 3 ++ actix-web-codegen/Cargo.toml | 1 + actix-web-codegen/src/route.rs | 2 + actix-web-codegen/tests/trybuild.rs | 1 + .../trybuild/route-malformed-path-fail.rs | 33 +++++++++++++++ .../trybuild/route-malformed-path-fail.stderr | 42 +++++++++++++++++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs create mode 100644 actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index fbf29cc7a..57ce36804 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -967,7 +967,10 @@ impl ResourceDef { _ => false, }) .unwrap_or_else(|| { - panic!(r#"path "{}" contains malformed dynamic segment"#, pattern) + panic!( + r#"pattern "{}" contains malformed dynamic segment"#, + pattern + ) }); let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1); diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index a8a901f72..4fd393b4d 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* In routing macros, paths are now validated at compile time. [#2350] + +[#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 4d0fd5e26..66f7acf6d 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -17,6 +17,7 @@ proc-macro = true quote = "1" syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" +actix-router = "0.5.0-beta.1" [dev-dependencies] actix-rt = "2.2" diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 747042527..c2f851a0e 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -3,6 +3,7 @@ extern crate proc_macro; use std::collections::HashSet; use std::convert::TryFrom; +use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; @@ -101,6 +102,7 @@ impl Args { match arg { NestedMeta::Lit(syn::Lit::Str(lit)) => match path { None => { + let _ = ResourceDef::new(lit.value()); path = Some(lit); } _ => { diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 12e848cf3..c97211e9f 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -10,6 +10,7 @@ fn compile_macros() { t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); + t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); t.pass("tests/trybuild/docstring-ok.rs"); } diff --git a/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs new file mode 100644 index 000000000..1258a6f2f --- /dev/null +++ b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs @@ -0,0 +1,33 @@ +use actix_web_codegen::get; + +#[get("/{")] +async fn zero() -> &'static str { + "malformed resource def" +} + +#[get("/{foo")] +async fn one() -> &'static str { + "malformed resource def" +} + +#[get("/{}")] +async fn two() -> &'static str { + "malformed resource def" +} + +#[get("/*")] +async fn three() -> &'static str { + "malformed resource def" +} + +#[get("/{tail:\\d+}*")] +async fn four() -> &'static str { + "malformed resource def" +} + +#[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] +async fn five() -> &'static str { + "malformed resource def" +} + +fn main() {} diff --git a/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr new file mode 100644 index 000000000..93c510109 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr @@ -0,0 +1,42 @@ +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:3:1 + | +3 | #[get("/{")] + | ^^^^^^^^^^^^ + | + = help: message: pattern "{" contains malformed dynamic segment + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:8:1 + | +8 | #[get("/{foo")] + | ^^^^^^^^^^^^^^^ + | + = help: message: pattern "{foo" contains malformed dynamic segment + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:13:1 + | +13 | #[get("/{}")] + | ^^^^^^^^^^^^^ + | + = help: message: Wrong path pattern: "/{}" regex parse error: + ((?s-m)^/(?P<>[^/]+))$ + ^ + error: empty capture group name + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:23:1 + | +23 | #[get("/{tail:\\d+}*")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: custom regex is not supported for tail match + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:28:1 + | +28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Only 16 dynamic segments are allowed, provided: 17 From 5128b1bdfc0c47fc744f2bc1f417ef5fd0e7f3c1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 30 Aug 2021 23:19:03 +0100 Subject: [PATCH 053/861] bump msrv to 1.51 --- .github/workflows/ci.yml | 2 +- CHANGES.md | 3 +++ README.md | 4 ++-- actix-files/CHANGES.md | 1 + actix-files/README.md | 4 ++-- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 4 ++-- actix-http/CHANGES.md | 3 +++ actix-http/README.md | 4 ++-- actix-http/src/body/mod.rs | 2 +- actix-http/src/h1/chunked.rs | 8 ++++---- actix-http/src/h1/dispatcher.rs | 2 +- actix-multipart/CHANGES.md | 1 + actix-multipart/README.md | 4 ++-- actix-router/CHANGES.md | 1 + actix-test/CHANGES.md | 1 + actix-web-actors/CHANGES.md | 1 + actix-web-actors/README.md | 4 ++-- actix-web-codegen/CHANGES.md | 1 + actix-web-codegen/README.md | 4 ++-- actix-web-codegen/tests/trybuild.rs | 2 +- awc/README.md | 2 +- clippy.toml | 2 +- src/lib.rs | 2 +- src/responder.rs | 6 +++--- src/types/query.rs | 4 ++-- 26 files changed, 43 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22b92759a..221d2fb40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } version: - - 1.46.0 # MSRV + - 1.51.0 # MSRV - stable - nightly diff --git a/CHANGES.md b/CHANGES.md index 88295ec12..5325caf48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ### Added * Re-export actix-service `ServiceFactory` in `dev` module. [#2325] +### Changes +* Minimum supported Rust version (MSRV) is now 1.51. + [#2325]: https://github.com/actix/actix-web/pull/2325 diff --git a/README.md b/README.md index 309a18466..33784d66a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web/4.0.0-beta.8) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.8)
@@ -32,7 +32,7 @@ * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://docs.rs/awc/) -* Runs on stable Rust 1.46+ +* Runs on stable Rust 1.51+ ## Documentation diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index db047c44c..533f72291 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 diff --git a/actix-files/README.md b/actix-files/README.md index 13c301c56..5815ef563 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.6)](https://docs.rs/actix-files/0.6.0-beta.6) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.6/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.6) @@ -15,4 +15,4 @@ - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) -- Minimum supported Rust version: 1.46 or later +- Minimum supported Rust version: 1.51 or later diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 1dbd9a15b..39b6a3a66 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 74260a352..099fb385d 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http-test) -- Minimum Supported Rust Version (MSRV): 1.46.0 +- Minimum Supported Rust Version (MSRV): 1.51.0 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9ed28105f..57c09d2d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,12 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Changes +* Minimum supported Rust version (MSRV) is now 1.51. ### Fixed * Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] [#2364]: https://github.com/actix/actix-web/pull/2364 + ## 3.0.0-beta.8 - 2021-08-09 ### Fixed * Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) diff --git a/actix-http/README.md b/actix-http/README.md index 5b06583bc..c509eaff8 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http/3.0.0-beta.9) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.9) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http) -- Minimum Supported Rust Version (MSRV): 1.46.0 +- Minimum Supported Rust Version (MSRV): 1.51.0 ## Example diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 8a08dbd2b..a60a8895c 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -80,7 +80,7 @@ mod tests { impl Body { pub(crate) fn get_ref(&self) -> &[u8] { match *self { - Body::Bytes(ref bin) => &bin, + Body::Bytes(ref bin) => bin, _ => panic!(), } } diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 1224ce08c..e5b734fff 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -40,7 +40,7 @@ impl ChunkedState { Size => ChunkedState::read_size(body, size), SizeLws => ChunkedState::read_size_lws(body), Extension => ChunkedState::read_extension(body), - SizeLf => ChunkedState::read_size_lf(body, size), + SizeLf => ChunkedState::read_size_lf(body, *size), Body => ChunkedState::read_body(body, size, buf), BodyCr => ChunkedState::read_body_cr(body), BodyLf => ChunkedState::read_body_lf(body), @@ -113,11 +113,11 @@ impl ChunkedState { } fn read_size_lf( rdr: &mut BytesMut, - size: &mut u64, + size: u64, ) -> Poll> { match byte!(rdr) { - b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), - b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), + b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)), + b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size LF", diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index deb25763c..aef765b89 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1060,7 +1060,7 @@ mod tests { fn stabilize_date_header(payload: &mut [u8]) { let mut from = 0; - while let Some(pos) = find_slice(&payload, b"date", from) { + while let Some(pos) = find_slice(payload, b"date", from) { payload[(from + pos)..(from + pos + 35)] .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); from += 35; diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 0b6affa3c..1e768ddf5 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 78855b815..aed16721c 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.5)](https://docs.rs/actix-multipart/0.4.0-beta.5) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-multipart) -- Minimum Supported Rust Version (MSRV): 1.46.0 +- Minimum Supported Rust Version (MSRV): 1.51.0 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 140d108e2..804f7778d 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -7,6 +7,7 @@ * Improve malformed path error message. [#384] * Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] * Prefix segments with trailing slashes define a trailing empty segment. [#2355] +* Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index fa554ba2e..dc76ba3fd 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index bf642ef95..084e7b272 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 5f8f78bde..2858d3f20 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.6)](https://docs.rs/actix-web-actors/4.0.0-beta.6) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-actors) -- Minimum supported Rust version: 1.46 or later +- Minimum supported Rust version: 1.51 or later diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 4fd393b4d..f0a56b30f 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx * In routing macros, paths are now validated at compile time. [#2350] +* Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 96e4cb51f..e69cfbbe5 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.3)](https://docs.rs/actix-web-codegen/0.5.0-beta.3) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) -- Minimum supported Rust version: 1.46 or later. +- Minimum supported Rust version: 1.51 or later. ## Compile Testing diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index c97211e9f..54bc1caec 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.46)] // MSRV +#[rustversion::stable(1.51)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/awc/README.md b/awc/README.md index dd08c6e10..fe91383ca 100644 --- a/awc/README.md +++ b/awc/README.md @@ -12,7 +12,7 @@ - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) -- Minimum Supported Rust Version (MSRV): 1.46.0 +- Minimum Supported Rust Version (MSRV): 1.51.0 ## Example diff --git a/clippy.toml b/clippy.toml index eb66960ac..829dd1c59 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.46" +msrv = "1.51" diff --git a/src/lib.rs b/src/lib.rs index 714c759cf..e7cf46361 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://docs.rs/awc/) -//! * Runs on stable Rust 1.46+ +//! * Runs on stable Rust 1.51+ //! //! # Crate Features //! * `cookies` - cookies support (enabled by default) diff --git a/src/responder.rs b/src/responder.rs index c5852a501..005bff03e 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -270,7 +270,7 @@ pub(crate) mod tests { impl BodyTest for Body { fn bin_ref(&self) -> &[u8] { match self { - Body::Bytes(ref bin) => &bin, + Body::Bytes(ref bin) => bin, _ => unreachable!("bug in test impl"), } } @@ -283,11 +283,11 @@ pub(crate) mod tests { fn bin_ref(&self) -> &[u8] { match self { ResponseBody::Body(ref b) => match b { - Body::Bytes(ref bin) => &bin, + Body::Bytes(ref bin) => bin, _ => unreachable!("bug in test impl"), }, ResponseBody::Other(ref b) => match b { - Body::Bytes(ref bin) => &bin, + Body::Bytes(ref bin) => bin, _ => unreachable!("bug in test impl"), }, } diff --git a/src/types/query.rs b/src/types/query.rs index 8762547e6..1e6f1111f 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -213,10 +213,10 @@ mod tests { #[actix_rt::test] async fn test_service_request_extract() { let req = TestRequest::with_uri("/name/user1/").to_srv_request(); - assert!(Query::::from_query(&req.query_string()).is_err()); + assert!(Query::::from_query(req.query_string()).is_err()); let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let mut s = Query::::from_query(&req.query_string()).unwrap(); + let mut s = Query::::from_query(req.query_string()).unwrap(); assert_eq!(s.id, "test"); assert_eq!( From ae35e69382805164704d8d7c79f41c85089b3d36 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 31 Aug 2021 02:52:29 +0100 Subject: [PATCH 054/861] use rust 1.51 features --- Cargo.toml | 1 + actix-http/src/body/body.rs | 24 +++++++++--------------- actix-http/src/body/response_body.rs | 9 ++------- actix-http/src/encoding/encoder.rs | 13 ++----------- actix-http/src/h1/utils.rs | 5 +---- src/middleware/logger.rs | 1 - 6 files changed, 15 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2ce46ee1..cee401363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ name = "actix_web" path = "src/lib.rs" [workspace] +resolver = "2" members = [ ".", "awc", diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index f04837d07..cd3e4c5c4 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -7,7 +7,7 @@ use std::{ }; use bytes::{Bytes, BytesMut}; -use futures_core::{ready, Stream}; +use futures_core::Stream; use crate::error::Error; @@ -74,14 +74,10 @@ impl MessageBody for AnyBody { } } - // TODO: MSRV 1.51: poll_map_err - AnyBody::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { - Some(Err(err)) => { - Poll::Ready(Some(Err(Error::new_body().with_cause(err)))) - } - Some(Ok(val)) => Poll::Ready(Some(Ok(val))), - None => Poll::Ready(None), - }, + AnyBody::Message(body) => body + .as_pin_mut() + .poll_next(cx) + .map_err(|err| Error::new_body().with_cause(err)), } } } @@ -223,11 +219,9 @@ impl MessageBody for BoxAnyBody { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - // TODO: MSRV 1.51: poll_map_err - match ready!(self.0.as_mut().poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(Error::new_body().with_cause(err)))), - Some(Ok(val)) => Poll::Ready(Some(Ok(val))), - None => Poll::Ready(None), - } + self.0 + .as_mut() + .poll_next(cx) + .map_err(|err| Error::new_body().with_cause(err)) } } diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs index 855c742f2..699ea9384 100644 --- a/actix-http/src/body/response_body.rs +++ b/actix-http/src/body/response_body.rs @@ -5,7 +5,7 @@ use std::{ }; use bytes::Bytes; -use futures_core::{ready, Stream}; +use futures_core::Stream; use pin_project::pin_project; use crate::error::Error; @@ -77,12 +77,7 @@ where cx: &mut Context<'_>, ) -> Poll> { match self.project() { - // TODO: MSRV 1.51: poll_map_err - ResponseBodyProj::Body(body) => match ready!(body.poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), - Some(Ok(val)) => Poll::Ready(Some(Ok(val))), - None => Poll::Ready(None), - }, + ResponseBodyProj::Body(body) => body.poll_next(cx).map_err(Into::into), ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 1e69990a0..c39c0e888 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -131,18 +131,9 @@ where Poll::Ready(Some(Ok(std::mem::take(b)))) } } - // TODO: MSRV 1.51: poll_map_err - EncoderBodyProj::Stream(b) => match ready!(b.poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Body(err)))), - Some(Ok(val)) => Poll::Ready(Some(Ok(val))), - None => Poll::Ready(None), - }, + EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body), EncoderBodyProj::BoxedStream(ref mut b) => { - match ready!(b.as_pin_mut().poll_next(cx)) { - Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Boxed(err)))), - Some(Ok(val)) => Poll::Ready(Some(Ok(val))), - None => Poll::Ready(None), - } + b.as_pin_mut().poll_next(cx).map_err(EncoderError::Boxed) } } } diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 523e652fd..5fd3cc21c 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -63,12 +63,9 @@ where .is_write_buf_full() { let next = - // TODO: MSRV 1.51: poll_map_err match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) { Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)), - Poll::Ready(Some(Err(err))) => { - return Poll::Ready(Err(err.into())) - } + Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, }; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 0f09b6ad6..9574b02f7 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -341,7 +341,6 @@ where ) -> Poll>> { let this = self.project(); - // TODO: MSRV 1.51: poll_map_err match ready!(this.body.poll_next(cx)) { Some(Ok(chunk)) => { *this.size += chunk.len(); From dade818ebaab441e8cc5d359068209daa002b488 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 31 Aug 2021 04:18:54 +0100 Subject: [PATCH 055/861] add middleware composition tests (#2375) --- actix-http/CHANGES.md | 2 ++ actix-http/src/body/message_body.rs | 4 --- actix-http/src/encoding/encoder.rs | 4 +-- actix-http/src/h1/utils.rs | 4 ++- src/middleware/mod.rs | 40 +++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 57c09d2d8..63172e56d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,8 +6,10 @@ ### Fixed * Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +* Remove `Into` bound on `Encoder` body types. [#2375] [#2364]: https://github.com/actix/actix-web/pull/2364 +[#2375]: https://github.com/actix/actix-web/pull/2375 ## 3.0.0-beta.8 - 2021-08-09 diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 2d2642ba7..edb4c550c 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -11,8 +11,6 @@ use bytes::{Bytes, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; -use crate::error::Error; - use super::BodySize; /// An interface for response bodies. @@ -47,7 +45,6 @@ impl MessageBody for () { impl MessageBody for Box where B: MessageBody + Unpin, - B::Error: Into, { type Error = B::Error; @@ -66,7 +63,6 @@ where impl MessageBody for Pin> where B: MessageBody, - B::Error: Into, { type Error = B::Error; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index c39c0e888..abd8cedba 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -29,7 +29,7 @@ use crate::{ header::{ContentEncoding, CONTENT_ENCODING}, HeaderValue, StatusCode, }, - Error, ResponseHead, + ResponseHead, }; use super::Writer; @@ -107,7 +107,6 @@ enum EncoderBody { impl MessageBody for EncoderBody where B: MessageBody, - B::Error: Into, { type Error = EncoderError; @@ -142,7 +141,6 @@ where impl MessageBody for Encoder where B: MessageBody, - B::Error: Into, { type Error = EncoderError; diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 5fd3cc21c..2547f4494 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -65,7 +65,9 @@ where let next = match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) { Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)), - Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), + Poll::Ready(Some(Err(err))) => { + return Poll::Ready(Err(err.into())) + } Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, }; diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 96a361fcf..d19cb64e9 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -19,3 +19,43 @@ mod compress; #[cfg(feature = "__compress")] pub use self::compress::Compress; + +#[cfg(test)] +mod tests { + use crate::{http::StatusCode, App}; + + use super::*; + + #[test] + fn common_combinations() { + // ensure there's no reason that the built-in middleware cannot compose + + let _ = App::new() + .wrap(Compat::new(Logger::default())) + .wrap(Condition::new(true, DefaultHeaders::new())) + .wrap(DefaultHeaders::new().header("X-Test2", "X-Value2")) + .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { + Ok(ErrorHandlerResponse::Response(res)) + })) + .wrap(Logger::default()) + .wrap(NormalizePath::new(TrailingSlash::Trim)); + + let _ = App::new() + .wrap(NormalizePath::new(TrailingSlash::Trim)) + .wrap(Logger::default()) + .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { + Ok(ErrorHandlerResponse::Response(res)) + })) + .wrap(DefaultHeaders::new().header("X-Test2", "X-Value2")) + .wrap(Condition::new(true, DefaultHeaders::new())) + .wrap(Compat::new(Logger::default())); + + #[cfg(feature = "__compress")] + { + let _ = App::new().wrap(Compress::default()).wrap(Logger::default()); + let _ = App::new().wrap(Logger::default()).wrap(Compress::default()); + let _ = App::new().wrap(Compat::new(Compress::default())); + let _ = App::new().wrap(Condition::new(true, Compat::new(Compress::default()))); + } + } +} From c50eef61664e0614255d02924d43f18c4630e447 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 31 Aug 2021 04:07:53 +0100 Subject: [PATCH 056/861] "deprecate" calls to NormalizePath::default --- MIGRATION.md | 3 ++- src/middleware/normalize.rs | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 785974366..9a70adb95 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,7 +3,8 @@ * The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when - using `NormalizePath::default()`. + using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. + It is advised that the `new` method be used instead. Before: `#[get("/test/")]` After: `#[get("/test")]` diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 219af1c6a..8ad0bb3f0 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -59,7 +59,7 @@ impl Default for TrailingSlash { /// /// # actix_web::rt::System::new().block_on(async { /// let app = App::new() -/// .wrap(middleware::NormalizePath::default()) +/// .wrap(middleware::NormalizePath::trim()) /// .route("/test", web::get().to(|| async { "test" })) /// .route("/unmatchable/", web::get().to(|| async { "unmatchable" })); /// @@ -85,13 +85,31 @@ impl Default for TrailingSlash { /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # }) /// ``` -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy)] pub struct NormalizePath(TrailingSlash); +impl Default for NormalizePath { + fn default() -> Self { + log::warn!( + "`NormalizePath::default()` is deprecated. The default trailing slash behavior changed \ + in v4 from `Always` to `Trim`. Update your call to `NormalizePath::new(...)`." + ); + + Self(TrailingSlash::Trim) + } +} + impl NormalizePath { /// Create new `NormalizePath` middleware with the specified trailing slash style. pub fn new(trailing_slash_style: TrailingSlash) -> Self { - NormalizePath(trailing_slash_style) + Self(trailing_slash_style) + } + + /// Constructs a new `NormalizePath` middleware with [trim](TrailingSlash::Trim) semantics. + /// + /// Use this instead of `NormalizePath::default()` to avoid deprecation warning. + pub fn trim() -> Self { + Self::new(TrailingSlash::Trim) } } From 7d01ece3556e77c0555f4e7da6c8699d8fc34fb1 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 31 Aug 2021 16:15:22 +0300 Subject: [PATCH 057/861] ResourceDef: support multiple-patterns as prefix (#2356) Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 4 + actix-router/src/resource.rs | 253 +++++++++++++++++------------------ 2 files changed, 128 insertions(+), 129 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 804f7778d..990382512 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -7,6 +7,9 @@ * Improve malformed path error message. [#384] * Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] * Prefix segments with trailing slashes define a trailing empty segment. [#2355] +* Support multi-pattern prefixes and joins. [#2356] +* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +* Support `build_resource_path` on multi-pattern resources. [#2356] * Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 @@ -14,6 +17,7 @@ [#380]: https://github.com/actix/actix-net/pull/380 [#384]: https://github.com/actix/actix-net/pull/384 [#2355]: https://github.com/actix/actix-web/pull/2355 +[#2356]: https://github.com/actix/actix-web/pull/2356 ## 0.5.0-beta.1 - 2021-07-20 diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 57ce36804..be54336e9 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -31,13 +31,13 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// # Pattern Format and Matching Behavior /// /// Resource pattern is defined as a string of zero or more _segments_ where each segment is -/// preceeded by a slash `/`. +/// preceded by a slash `/`. /// /// This means that pattern string __must__ either be empty or begin with a slash (`/`). /// This also implies that a trailing slash in pattern defines an empty segment. /// For example, the pattern `"/user/"` has two segments: `["user", ""]` /// -/// A key point to undertand is that `ResourceDef` matches segments, not strings. +/// A key point to underhand is that `ResourceDef` matches segments, not strings. /// It matches segments individually. /// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`, /// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`. @@ -220,17 +220,15 @@ pub struct ResourceDef { name: Option, /// Pattern that generated the resource definition. - /// - /// `None` when pattern type is `DynamicSet`. patterns: Patterns, + is_prefix: bool, + /// Pattern type. pat_type: PatternType, /// List of segments that compose the pattern, in order. - /// - /// `None` when pattern type is `DynamicSet`. - segments: Option>, + segments: Vec, } #[derive(Debug, Clone, PartialEq)] @@ -248,9 +246,6 @@ enum PatternType { /// Single constant/literal segment. Static(String), - /// Single constant/literal prefix segment. - Prefix(String), - /// Single regular expression and list of dynamic segment names. Dynamic(Regex, Vec<&'static str>), @@ -284,45 +279,7 @@ impl ResourceDef { /// ``` pub fn new(paths: T) -> Self { profile_method!(new); - - match paths.patterns() { - Patterns::Single(pattern) => ResourceDef::from_single_pattern(&pattern, false), - - // since zero length pattern sets are possible - // just return a useless `ResourceDef` - Patterns::List(patterns) if patterns.is_empty() => ResourceDef { - id: 0, - name: None, - patterns: Patterns::List(patterns), - pat_type: PatternType::DynamicSet(RegexSet::empty(), Vec::new()), - segments: None, - }, - - Patterns::List(patterns) => { - let mut re_set = Vec::with_capacity(patterns.len()); - let mut pattern_data = Vec::new(); - - for pattern in &patterns { - match ResourceDef::parse(pattern, false, true) { - (PatternType::Dynamic(re, names), _) => { - re_set.push(re.as_str().to_owned()); - pattern_data.push((re, names)); - } - _ => unreachable!(), - } - } - - let pattern_re_set = RegexSet::new(re_set).unwrap(); - - ResourceDef { - id: 0, - name: None, - patterns: Patterns::List(patterns), - pat_type: PatternType::DynamicSet(pattern_re_set, pattern_data), - segments: None, - } - } - } + Self::new2(paths, false) } /// Constructs a new resource definition using a pattern that performs prefix matching. @@ -348,9 +305,9 @@ impl ResourceDef { /// assert!(!resource.is_match("user/123/stars")); /// assert!(!resource.is_match("/foo")); /// ``` - pub fn prefix(path: &str) -> Self { + pub fn prefix(paths: T) -> Self { profile_method!(prefix); - ResourceDef::from_single_pattern(path, true) + ResourceDef::new2(paths, true) } /// Constructs a new resource definition using a string pattern that performs prefix matching, @@ -375,7 +332,7 @@ impl ResourceDef { /// ``` pub fn root_prefix(path: &str) -> Self { profile_method!(root_prefix); - ResourceDef::prefix(&insert_slash(path)) + ResourceDef::prefix(insert_slash(path).into_owned()) } /// Returns a numeric resource ID. @@ -453,17 +410,14 @@ impl ResourceDef { /// assert!(!ResourceDef::new("/user").is_prefix()); /// ``` pub fn is_prefix(&self) -> bool { - match &self.pat_type { - PatternType::Prefix(_) => true, - PatternType::Dynamic(re, _) if !re.as_str().ends_with('$') => true, - _ => false, - } + self.is_prefix } /// Returns the pattern string that generated the resource definition. /// - /// Returns `None` if definition was constructed with multiple patterns. - /// See [`patterns_iter`][Self::pattern_iter]. + /// If definition is constructed with multiple patterns, the first pattern is returned. To get + /// all patterns, use [`patterns_iter`][Self::pattern_iter]. If resource has 0 patterns, + /// returns `None`. /// /// # Examples /// ``` @@ -472,11 +426,11 @@ impl ResourceDef { /// assert_eq!(resource.pattern().unwrap(), "/user/{id}"); /// /// let mut resource = ResourceDef::new(["/profile", "/user/{id}"]); - /// assert!(resource.pattern().is_none()); + /// assert_eq!(resource.pattern(), Some("/profile")); pub fn pattern(&self) -> Option<&str> { match &self.patterns { Patterns::Single(pattern) => Some(pattern.as_str()), - Patterns::List(_) => None, + Patterns::List(patterns) => patterns.first().map(AsRef::as_ref), } } @@ -563,8 +517,8 @@ impl ResourceDef { .collect::>(); match patterns.len() { - 1 => ResourceDef::from_single_pattern(&patterns[0], other.is_prefix()), - _ => ResourceDef::new(patterns), + 1 => ResourceDef::new2(&patterns[0], other.is_prefix()), + _ => ResourceDef::new2(patterns, other.is_prefix()), } } @@ -609,11 +563,10 @@ impl ResourceDef { // `self.find_match(path).is_some()` // but this skips some checks and uses potentially faster regex methods - match self.pat_type { - PatternType::Static(ref s) => s == path, - PatternType::Prefix(ref prefix) => is_prefix(prefix, path), - PatternType::Dynamic(ref re, _) => re.is_match(path), - PatternType::DynamicSet(ref re, _) => re.is_match(path), + match &self.pat_type { + PatternType::Static(pattern) => self.static_match(pattern, path).is_some(), + PatternType::Dynamic(re, _) => re.is_match(path), + PatternType::DynamicSet(re, _) => re.is_match(path), } } @@ -656,11 +609,7 @@ impl ResourceDef { profile_method!(find_match); match &self.pat_type { - PatternType::Static(segment) if path == segment => Some(segment.len()), - PatternType::Static(_) => None, - - PatternType::Prefix(prefix) if is_prefix(prefix, path) => Some(prefix.len()), - PatternType::Prefix(_) => None, + PatternType::Static(pattern) => self.static_match(pattern, path), PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()), @@ -753,10 +702,10 @@ impl ResourceDef { let path_str = path.path(); let (matched_len, matched_vars) = match &self.pat_type { - PatternType::Static(_) | PatternType::Prefix(_) => { + PatternType::Static(pattern) => { profile_section!(pattern_static_or_prefix); - match self.find_match(path_str) { + match self.static_match(pattern, path_str) { Some(len) => (len, None), None => return false, } @@ -844,13 +793,10 @@ impl ResourceDef { F: FnMut(&str) -> Option, I: AsRef, { - for el in match self.segments { - Some(ref segments) => segments, - None => return false, - } { - match *el { - PatternSegment::Const(ref val) => path.push_str(val), - PatternSegment::Var(ref name) => match vars(name) { + for segment in &self.segments { + match segment { + PatternSegment::Const(val) => path.push_str(val), + PatternSegment::Var(name) => match vars(name) { Some(val) => path.push_str(val.as_ref()), _ => return false, }, @@ -864,8 +810,8 @@ impl ResourceDef { /// /// Returns `true` on success. /// - /// Resource paths can not be built from multi-pattern resources; this call will always return - /// false and will not add anything to the string buffer. + /// For multi-pattern resources, the first pattern is used under the assumption that it would be + /// equivalent to any other choice. /// /// # Examples /// ``` @@ -890,8 +836,8 @@ impl ResourceDef { /// /// Returns `true` on success. /// - /// Resource paths can not be built from multi-pattern resources; this call will always return - /// false and will not add anything to the string buffer. + /// For multi-pattern resources, the first pattern is used under the assumption that it would be + /// equivalent to any other choice. /// /// # Examples /// ``` @@ -921,19 +867,69 @@ impl ResourceDef { self.build_resource_path(path, |name| values.get(name).map(AsRef::::as_ref)) } - /// Parse path pattern and create a new instance. - fn from_single_pattern(pattern: &str, is_prefix: bool) -> Self { - profile_method!(from_single_pattern); + /// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. + fn static_match(&self, pattern: &str, path: &str) -> Option { + let rem = path.strip_prefix(pattern)?; - let pattern = pattern.to_owned(); - let (pat_type, segments) = ResourceDef::parse(&pattern, is_prefix, false); + match self.is_prefix { + // resource is not a prefix so an exact match is needed + false if rem.is_empty() => Some(pattern.len()), + + // resource is a prefix so rem should start with a path delimiter + true if rem.is_empty() || rem.starts_with('/') => Some(pattern.len()), + + // otherwise, no match + _ => None, + } + } + + fn new2(paths: T, is_prefix: bool) -> Self { + profile_method!(new2); + + let patterns = paths.patterns(); + let (pat_type, segments) = match &patterns { + Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false), + + // since zero length pattern sets are possible + // just return a useless `ResourceDef` + Patterns::List(patterns) if patterns.is_empty() => ( + PatternType::DynamicSet(RegexSet::empty(), Vec::new()), + Vec::new(), + ), + + Patterns::List(patterns) => { + let mut re_set = Vec::with_capacity(patterns.len()); + let mut pattern_data = Vec::new(); + let mut segments = None; + + for pattern in patterns { + match ResourceDef::parse(pattern, is_prefix, true) { + (PatternType::Dynamic(re, names), segs) => { + re_set.push(re.as_str().to_owned()); + pattern_data.push((re, names)); + segments.get_or_insert(segs); + } + _ => unreachable!(), + } + } + + let pattern_re_set = RegexSet::new(re_set).unwrap(); + let segments = segments.unwrap_or_else(Vec::new); + + ( + PatternType::DynamicSet(pattern_re_set, pattern_data), + segments, + ) + } + }; ResourceDef { id: 0, name: None, - patterns: Patterns::Single(pattern), + patterns, + is_prefix, pat_type, - segments: Some(segments), + segments, } } @@ -1023,20 +1019,15 @@ impl ResourceDef { ) -> (PatternType, Vec) { profile_method!(parse); - let mut unprocessed = pattern; - - if !force_dynamic && unprocessed.find('{').is_none() && !unprocessed.ends_with('*') { + if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') { // pattern is static - - let tp = if is_prefix { - PatternType::Prefix(unprocessed.to_owned()) - } else { - PatternType::Static(unprocessed.to_owned()) - }; - - return (tp, vec![PatternSegment::Const(unprocessed.to_owned())]); + return ( + PatternType::Static(pattern.to_owned()), + vec![PatternSegment::Const(pattern.to_owned())], + ); } + let mut unprocessed = pattern; let mut segments = Vec::new(); let mut re = format!("{}^", REGEX_FLAGS); let mut dyn_segment_count = 0; @@ -1137,18 +1128,7 @@ impl Eq for ResourceDef {} impl PartialEq for ResourceDef { fn eq(&self, other: &ResourceDef) -> bool { - self.patterns == other.patterns - && match &self.pat_type { - PatternType::Static(_) => matches!(&other.pat_type, PatternType::Static(_)), - PatternType::Prefix(_) => matches!(&other.pat_type, PatternType::Prefix(_)), - PatternType::Dynamic(re, _) => match &other.pat_type { - PatternType::Dynamic(other_re, _) => re.as_str() == other_re.as_str(), - _ => false, - }, - PatternType::DynamicSet(_, _) => { - matches!(&other.pat_type, PatternType::DynamicSet(..)) - } - } + self.patterns == other.patterns && self.is_prefix == other.is_prefix } } @@ -1183,15 +1163,6 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { } } -/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. -fn is_prefix(prefix: &str, path: &str) -> bool { - match path.strip_prefix(prefix) { - // Ensure the match ends at segment boundary - Some(rem) if rem.is_empty() || rem.starts_with('/') => true, - _ => false, - } -} - #[cfg(test)] mod tests { use super::*; @@ -1376,6 +1347,24 @@ mod tests { assert!(!re.is_match("/user/2345/sdg")); } + #[test] + fn dynamic_set_prefix() { + let re = ResourceDef::prefix(vec!["/u/{id}", "/{id:[[:digit:]]{3}}"]); + + assert_eq!(re.find_match("/u/abc"), Some(6)); + assert_eq!(re.find_match("/u/abc/123"), Some(6)); + assert_eq!(re.find_match("/s/user/profile"), None); + + assert_eq!(re.find_match("/123"), Some(4)); + assert_eq!(re.find_match("/123/456"), Some(4)); + assert_eq!(re.find_match("/12345"), None); + + let mut path = Path::new("/151/res"); + assert!(re.capture_match_info(&mut path)); + assert_eq!(path.get("id").unwrap(), "151"); + assert_eq!(path.unprocessed(), "/res"); + } + #[test] fn parse_tail() { let re = ResourceDef::new("/user/-{id}*"); @@ -1602,10 +1591,11 @@ mod tests { } #[test] - fn multi_pattern_cannot_build_path() { + fn multi_pattern_build_path() { let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); let mut s = String::new(); - assert!(!resource.resource_path_from_iter(&mut s, &mut ["123"].iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["123"].iter())); + assert_eq!(s, "/user/123"); } #[test] @@ -1738,8 +1728,12 @@ mod tests { join_test!("", "" => "", "/hello", "/"); join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123"); - join_test!("", "/user"=> "", "/user", "foo", "/user11", "user", "user/123"); - join_test!("/user", "/xx"=> "", "", "/", "/user", "/xx", "/userxx", "/user/xx"); + join_test!("", "/user" => "", "/user", "foo", "/user11", "user", "user/123"); + join_test!("/user", "/xx" => "", "", "/", "/user", "/xx", "/userxx", "/user/xx"); + + join_test!(["/ver/{v}", "/v{v}"], ["/req/{req}", "/{req}"] => "/v1/abc", + "/ver/1/abc", "/v1/req/abc", "/ver/1/req/abc", "/v1/abc/def", + "/ver1/req/abc/def", "", "/", "/v1/"); } #[test] @@ -1777,6 +1771,7 @@ mod tests { match_methods_agree!(prefix "" => "", "/", "/foo"); match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo"); match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234"); + match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1"); } #[test] From 373b3f91dff58ac6c5b1158206e7380376906541 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 1 Sep 2021 06:48:43 +0300 Subject: [PATCH 058/861] rework `ResourceMap` internals (#2337) --- src/app_service.rs | 4 +- src/request.rs | 9 +- src/rmap.rs | 422 ++++++++++++++++++++++++--------------------- 3 files changed, 233 insertions(+), 202 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index ce52543b8..cf34b302e 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -79,7 +79,7 @@ where .into_iter() .for_each(|mut srv| srv.register(&mut config)); - let mut rmap = ResourceMap::new(ResourceDef::new("")); + let mut rmap = ResourceMap::new(ResourceDef::prefix("")); let (config, services) = config.into_services(); @@ -104,7 +104,7 @@ where // complete ResourceMap tree creation let rmap = Rc::new(rmap); - rmap.finish(rmap.clone()); + ResourceMap::finish(&rmap); // construct all async data factory futures let factory_futs = join_all(self.async_data_factories.iter().map(|f| f())); diff --git a/src/request.rs b/src/request.rs index 59850b4ca..c25a5397a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -511,7 +511,7 @@ mod tests { let mut res = ResourceDef::new("/user/{name}.{ext}"); res.set_name("index"); - let mut rmap = ResourceMap::new(ResourceDef::new("")); + let mut rmap = ResourceMap::new(ResourceDef::prefix("")); rmap.add(&mut res, None); assert!(rmap.has_resource("/user/test.html")); assert!(!rmap.has_resource("/test/unknown")); @@ -541,7 +541,7 @@ mod tests { let mut rdef = ResourceDef::new("/index.html"); rdef.set_name("index"); - let mut rmap = ResourceMap::new(ResourceDef::new("")); + let mut rmap = ResourceMap::new(ResourceDef::prefix("")); rmap.add(&mut rdef, None); assert!(rmap.has_resource("/index.html")); @@ -562,7 +562,7 @@ mod tests { let mut rdef = ResourceDef::new("/index.html"); rdef.set_name("index"); - let mut rmap = ResourceMap::new(ResourceDef::new("")); + let mut rmap = ResourceMap::new(ResourceDef::prefix("")); rmap.add(&mut rdef, None); assert!(rmap.has_resource("/index.html")); @@ -581,9 +581,8 @@ mod tests { rdef.set_name("youtube"); - let mut rmap = ResourceMap::new(ResourceDef::new("")); + let mut rmap = ResourceMap::new(ResourceDef::prefix("")); rmap.add(&mut rdef, None); - assert!(rmap.has_resource("https://youtube.com/watch/unknown")); let req = TestRequest::default().rmap(rmap).to_http_request(); let url = req.url_for("youtube", &["oHg5SJYRHA0"]); diff --git a/src/rmap.rs b/src/rmap.rs index 0ee4de47e..8466eda28 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -10,43 +10,75 @@ use crate::request::HttpRequest; #[derive(Clone, Debug)] pub struct ResourceMap { - root: ResourceDef, + pattern: ResourceDef, + + /// Named resources within the tree or, for external resources, + /// it points to isolated nodes outside the tree. + named: AHashMap>, + parent: RefCell>, - named: AHashMap, - patterns: Vec<(ResourceDef, Option>)>, + + /// Must be `None` for "edge" nodes. + nodes: Option>>, } impl ResourceMap { + /// Creates a _container_ node in the `ResourceMap` tree. pub fn new(root: ResourceDef) -> Self { ResourceMap { - root, - parent: RefCell::new(Weak::new()), + pattern: root, named: AHashMap::default(), - patterns: Vec::new(), + parent: RefCell::new(Weak::new()), + nodes: Some(Vec::new()), } } + /// Adds a (possibly nested) resource. + /// + /// To add a non-prefix pattern, `nested` must be `None`. + /// To add external resource, supply a pattern without a leading `/`. + /// The root pattern of `nested`, if present, should match `pattern`. pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option>) { - pattern.set_id(self.patterns.len() as u16); - self.patterns.push((pattern.clone(), nested)); - if let Some(name) = pattern.name() { - self.named.insert(name.to_owned(), pattern.clone()); + pattern.set_id(self.nodes.as_ref().unwrap().len() as u16); + + if let Some(new_node) = nested { + assert_eq!(&new_node.pattern, pattern, "`patern` and `nested` mismatch"); + self.named.extend(new_node.named.clone().into_iter()); + self.nodes.as_mut().unwrap().push(new_node); + } else { + let new_node = Rc::new(ResourceMap { + pattern: pattern.clone(), + named: AHashMap::default(), + parent: RefCell::new(Weak::new()), + nodes: None, + }); + + if let Some(name) = pattern.name() { + self.named.insert(name.to_owned(), Rc::clone(&new_node)); + } + + let is_external = match pattern.pattern() { + Some(p) => !p.is_empty() && !p.starts_with('/'), + None => false, + }; + + // Don't add external resources to the tree + if !is_external { + self.nodes.as_mut().unwrap().push(new_node); + } } } - pub(crate) fn finish(&self, current: Rc) { - for (_, nested) in &self.patterns { - if let Some(ref nested) = nested { - *nested.parent.borrow_mut() = Rc::downgrade(¤t); - nested.finish(nested.clone()); - } + pub(crate) fn finish(self: &Rc) { + for node in self.nodes.iter().flatten() { + node.parent.replace(Rc::downgrade(self)); + ResourceMap::finish(node); } } /// Generate url for named resource /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. + /// Check [`HttpRequest::url_for`] for detailed information. pub fn url_for( &self, req: &HttpRequest, @@ -57,197 +89,97 @@ impl ResourceMap { U: IntoIterator, I: AsRef, { - let mut path = String::new(); let mut elements = elements.into_iter(); - if self.patterns_for(name, &mut path, &mut elements)?.is_some() { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } + let path = self + .named + .get(name) + .ok_or(UrlGenerationError::ResourceNotFound)? + .root_rmap_fn(String::with_capacity(24), |mut acc, node| { + node.pattern + .resource_path_from_iter(&mut acc, &mut elements) + .then(|| acc) + }) + .ok_or(UrlGenerationError::NotEnoughElements)?; + + if path.starts_with('/') { + let conn = req.connection_info(); + Ok(Url::parse(&format!( + "{}://{}{}", + conn.scheme(), + conn.host(), + path + ))?) } else { - Err(UrlGenerationError::ResourceNotFound) + Ok(Url::parse(&path)?) } } pub fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(pat_len) = pattern.find_match(path) { - return rmap.has_resource(&path[pat_len..]); - } - } else if pattern.is_match(path) || pattern.pattern() == Some("") && path == "/" { - return true; - } - } - false + self.find_matching_node(path).is_some() } /// Returns the name of the route that matches the given path or None if no full match - /// is possible. + /// is possible or the matching resource is not named. pub fn match_name(&self, path: &str) -> Option<&str> { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.find_match(path) { - return rmap.match_name(&path[plen..]); - } - } else if pattern.is_match(path) { - return pattern.name(); - } - } - - None + self.find_matching_node(path)?.pattern.name() } /// Returns the full resource pattern matched against a path or None if no full match /// is possible. pub fn match_pattern(&self, path: &str) -> Option { - let path = if path.is_empty() { "/" } else { path }; - - // ensure a full match exists - if !self.has_resource(path) { - return None; - } - - Some(self.traverse_resource_pattern(path)) + self.find_matching_node(path)?.root_rmap_fn( + String::with_capacity(24), + |mut acc, node| { + acc.push_str(node.pattern.pattern()?); + Some(acc) + }, + ) } - /// Takes remaining path and tries to match it up against a resource definition within the - /// current resource map recursively, returning a concatenation of all resource prefixes and - /// patterns matched in the tree. - /// - /// Should only be used after checking the resource exists in the map so that partial match - /// patterns are not returned. - fn traverse_resource_pattern(&self, remaining: &str) -> String { - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(prefix_len) = pattern.find_match(remaining) { - // TODO: think about unwrap_or - let prefix = pattern.pattern().unwrap_or("").to_owned(); - - return [ - prefix, - rmap.traverse_resource_pattern(&remaining[prefix_len..]), - ] - .concat(); - } - } else if pattern.is_match(remaining) { - // TODO: think about unwrap_or - return pattern.pattern().unwrap_or("").to_owned(); - } - } - - String::new() + fn find_matching_node(&self, path: &str) -> Option<&ResourceMap> { + self._find_matching_node(path).flatten() } - fn patterns_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> + /// Returns `None` if root pattern doesn't match; + /// `Some(None)` if root pattern matches but there is no matching child pattern. + /// Don't search sideways when `Some(none)` is returned. + fn _find_matching_node(&self, path: &str) -> Option> { + let matched_len = self.pattern.find_match(path)?; + let path = &path[matched_len..]; + + Some(match &self.nodes { + // find first sub-node to match remaining path + Some(nodes) => nodes + .iter() + .filter_map(|node| node._find_matching_node(path)) + .next() + .flatten(), + + // only terminate at edge nodes + None => Some(self), + }) + } + + /// Find `self`'s highest ancestor and then run `F`, providing `B`, in that rmap context. + fn root_rmap_fn(&self, init: B, mut f: F) -> Option where - U: Iterator, - I: AsRef, + F: FnMut(B, &ResourceMap) -> Option, { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } + self._root_rmap_fn(init, &mut f) } - fn pattern_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> + /// Run `F`, providing `B`, if `self` is top-level resource map, else recurse to parent map. + fn _root_rmap_fn(&self, init: B, f: &mut F) -> Option where - U: Iterator, - I: AsRef, + F: FnMut(B, &ResourceMap) -> Option, { - if let Some(pattern) = self.named.get(name) { - if pattern - .pattern() - .map(|pat| pat.starts_with('/')) - .unwrap_or(false) - { - self.fill_root(path, elements)?; - } + let data = match self.parent.borrow().upgrade() { + Some(ref parent) => parent._root_rmap_fn(init, f)?, + None => init, + }; - if pattern.resource_path_from_iter(path, elements) { - Ok(Some(())) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } else { - for (_, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - } - Ok(None) - } - } - - fn fill_root( - &self, - path: &mut String, - elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = self.parent.borrow().upgrade() { - parent.fill_root(path, elements)?; - } - - if self.root.resource_path_from_iter(path, elements) { - Ok(()) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } - - fn parent_pattern_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = self.parent.borrow().upgrade() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - if pattern.resource_path_from_iter(path, elements) { - Ok(Some(())) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } + f(data, self) } } @@ -259,7 +191,7 @@ mod tests { fn extract_matched_pattern() { let mut root = ResourceMap::new(ResourceDef::root_prefix("")); - let mut user_map = ResourceMap::new(ResourceDef::root_prefix("")); + let mut user_map = ResourceMap::new(ResourceDef::root_prefix("/user/{id}")); user_map.add(&mut ResourceDef::new("/"), None); user_map.add(&mut ResourceDef::new("/profile"), None); user_map.add(&mut ResourceDef::new("/article/{id}"), None); @@ -275,9 +207,10 @@ mod tests { &mut ResourceDef::root_prefix("/user/{id}"), Some(Rc::new(user_map)), ); + root.add(&mut ResourceDef::new("/info"), None); let root = Rc::new(root); - root.finish(Rc::clone(&root)); + ResourceMap::finish(&root); // sanity check resource map setup @@ -288,7 +221,7 @@ mod tests { assert!(root.has_resource("/v2")); assert!(!root.has_resource("/v33")); - assert!(root.has_resource("/user/22")); + assert!(!root.has_resource("/user/22")); assert!(root.has_resource("/user/22/")); assert!(root.has_resource("/user/22/profile")); @@ -336,7 +269,7 @@ mod tests { rdef.set_name("root_info"); root.add(&mut rdef, None); - let mut user_map = ResourceMap::new(ResourceDef::root_prefix("")); + let mut user_map = ResourceMap::new(ResourceDef::root_prefix("/user/{id}")); let mut rdef = ResourceDef::new("/"); user_map.add(&mut rdef, None); @@ -350,14 +283,14 @@ mod tests { ); let root = Rc::new(root); - root.finish(Rc::clone(&root)); + ResourceMap::finish(&root); // sanity check resource map setup assert!(root.has_resource("/info")); assert!(!root.has_resource("/bar")); - assert!(root.has_resource("/user/22")); + assert!(!root.has_resource("/user/22")); assert!(root.has_resource("/user/22/")); assert!(root.has_resource("/user/22/post/55")); @@ -377,7 +310,7 @@ mod tests { // ref: https://github.com/actix/actix-web/issues/1582 let mut root = ResourceMap::new(ResourceDef::root_prefix("")); - let mut user_map = ResourceMap::new(ResourceDef::root_prefix("")); + let mut user_map = ResourceMap::new(ResourceDef::root_prefix("/user/{id}")); user_map.add(&mut ResourceDef::new("/"), None); user_map.add(&mut ResourceDef::new("/profile"), None); user_map.add(&mut ResourceDef::new("/article/{id}"), None); @@ -393,20 +326,119 @@ mod tests { ); let root = Rc::new(root); - root.finish(Rc::clone(&root)); + ResourceMap::finish(&root); // check root has no parent assert!(root.parent.borrow().upgrade().is_none()); // check child has parent reference - assert!(root.patterns[0].1.is_some()); + assert!(root.nodes.as_ref().unwrap()[0] + .parent + .borrow() + .upgrade() + .is_some()); // check child's parent root id matches root's root id - assert_eq!( - root.patterns[0].1.as_ref().unwrap().root.id(), - root.root.id() - ); + assert!(Rc::ptr_eq( + &root.nodes.as_ref().unwrap()[0] + .parent + .borrow() + .upgrade() + .unwrap(), + &root + )); let output = format!("{:?}", root); assert!(output.starts_with("ResourceMap {")); assert!(output.ends_with(" }")); } + + #[test] + fn short_circuit() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut user_root = ResourceDef::prefix("/user"); + let mut user_map = ResourceMap::new(user_root.clone()); + user_map.add(&mut ResourceDef::new("/u1"), None); + user_map.add(&mut ResourceDef::new("/u2"), None); + + root.add(&mut ResourceDef::new("/user/u3"), None); + root.add(&mut user_root, Some(Rc::new(user_map))); + root.add(&mut ResourceDef::new("/user/u4"), None); + + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + assert!(rmap.has_resource("/user/u1")); + assert!(rmap.has_resource("/user/u2")); + assert!(rmap.has_resource("/user/u3")); + assert!(!rmap.has_resource("/user/u4")); + } + + #[test] + fn url_for() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut user_scope_rdef = ResourceDef::prefix("/user"); + let mut user_scope_map = ResourceMap::new(user_scope_rdef.clone()); + + let mut user_rdef = ResourceDef::new("/{user_id}"); + let mut user_map = ResourceMap::new(user_rdef.clone()); + + let mut post_rdef = ResourceDef::new("/post/{sub_id}"); + post_rdef.set_name("post"); + + user_map.add(&mut post_rdef, None); + user_scope_map.add(&mut user_rdef, Some(Rc::new(user_map))); + root.add(&mut user_scope_rdef, Some(Rc::new(user_scope_map))); + + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + let mut req = crate::test::TestRequest::default(); + req.set_server_hostname("localhost:8888"); + let req = req.to_http_request(); + + let url = rmap + .url_for(&req, "post", &["u123", "foobar"]) + .unwrap() + .to_string(); + assert_eq!(url, "http://localhost:8888/user/u123/post/foobar"); + + assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); + } + + #[test] + fn external_resource_with_no_name() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut rdef = ResourceDef::new("https://duck.com/{query}"); + root.add(&mut rdef, None); + + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + assert!(!rmap.has_resource("https://duck.com/abc")); + } + + #[test] + fn external_resource_with_name() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut rdef = ResourceDef::new("https://duck.com/{query}"); + rdef.set_name("duck"); + root.add(&mut rdef, None); + + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + assert!(!rmap.has_resource("https://duck.com/abc")); + + let mut req = crate::test::TestRequest::default(); + req.set_server_hostname("localhost:8888"); + let req = req.to_http_request(); + + assert_eq!( + rmap.url_for(&req, "duck", &["abcd"]).unwrap().to_string(), + "https://duck.com/abcd" + ); + } } From ddc8c16cb34c9730b9a4922413fbf027e401052d Mon Sep 17 00:00:00 2001 From: Arthur Le Moigne Date: Wed, 1 Sep 2021 10:08:29 +0200 Subject: [PATCH 059/861] Fix quality parse error in Accept-Encoding HTTP header (#2344) --- CHANGES.md | 7 +- actix-http/CHANGES.md | 5 +- actix-http/src/encoding/decoder.rs | 3 +- .../src/header/shared/content_encoding.rs | 56 ++--- actix-http/src/header/shared/quality_item.rs | 29 ++- src/middleware/compress.rs | 217 +++++++++++++++--- tests/test_server.rs | 19 ++ 7 files changed, 259 insertions(+), 77 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5325caf48..217ec4f78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,10 +4,15 @@ ### Added * Re-export actix-service `ServiceFactory` in `dev` module. [#2325] -### Changes +### Changed * Minimum supported Rust version (MSRV) is now 1.51. +* Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] + +### Fixed +* Fix quality parse error in Accept-Encoding header. [#2344] [#2325]: https://github.com/actix/actix-web/pull/2325 +[#2344]: https://github.com/actix/actix-web/pull/2344 ## 4.0.0-beta.8 - 2021-06-26 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 63172e56d..f4efef54a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,22 +1,23 @@ # Changes ## Unreleased - 2021-xx-xx -### Changes +### Changed * Minimum supported Rust version (MSRV) is now 1.51. ### Fixed * Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] * Remove `Into` bound on `Encoder` body types. [#2375] +* Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 +[#2344]: https://github.com/actix/actix-web/pull/2344 ## 3.0.0-beta.8 - 2021-08-09 ### Fixed * Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) - ## 3.0.0-beta.8 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index d3e304836..81e97d916 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,6 +1,7 @@ //! Stream decoders. use std::{ + convert::TryFrom, future::Future, io::{self, Write as _}, pin::Pin, @@ -80,7 +81,7 @@ where let encoding = headers .get(&CONTENT_ENCODING) .and_then(|val| val.to_str().ok()) - .map(ContentEncoding::from) + .and_then(|x| ContentEncoding::try_from(x).ok()) .unwrap_or(ContentEncoding::Identity); Self::new(stream, encoding) diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index b9c1d2795..375e8c2fa 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, str::FromStr}; +use std::{convert::TryFrom, error, fmt, str::FromStr}; use http::header::InvalidHeaderValue; @@ -8,6 +8,20 @@ use crate::{ HttpMessage, }; +/// Error return when a content encoding is unknown. +/// +/// Example: 'compress' +#[derive(Debug)] +pub struct ContentEncodingParseError; + +impl fmt::Display for ContentEncodingParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Unsupported content encoding") + } +} + +impl error::Error for ContentEncodingParseError {} + /// Represents a supported content encoding. #[derive(Copy, Clone, PartialEq, Debug)] pub enum ContentEncoding { @@ -37,7 +51,7 @@ impl ContentEncoding { matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) } - /// Convert content encoding to string + /// Convert content encoding to string. #[inline] pub fn as_str(self) -> &'static str { match self { @@ -48,18 +62,6 @@ impl ContentEncoding { 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, - ContentEncoding::Zstd => 0.0, - } - } } impl Default for ContentEncoding { @@ -69,31 +71,33 @@ impl Default for ContentEncoding { } impl FromStr for ContentEncoding { - type Err = Infallible; + type Err = ContentEncodingParseError; 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 + Ok(ContentEncoding::Br) } else if val.eq_ignore_ascii_case("gzip") { - ContentEncoding::Gzip + Ok(ContentEncoding::Gzip) } else if val.eq_ignore_ascii_case("deflate") { - ContentEncoding::Deflate + Ok(ContentEncoding::Deflate) } else if val.eq_ignore_ascii_case("zstd") { - ContentEncoding::Zstd + Ok(ContentEncoding::Zstd) } else { - ContentEncoding::default() + Err(ContentEncodingParseError) } } } +impl TryFrom<&str> for ContentEncoding { + type Error = ContentEncodingParseError; + + fn try_from(val: &str) -> Result { + val.parse() + } +} + impl IntoHeaderValue for ContentEncoding { type Error = InvalidHeaderValue; diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 240a0afa2..63fa02e7b 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,11 +1,14 @@ use std::{ cmp, convert::{TryFrom, TryInto}, - fmt, str, + fmt, + str::{self, FromStr}, }; use derive_more::{Display, Error}; +use crate::error::ParseError; + const MAX_QUALITY: u16 = 1000; const MAX_FLOAT_QUALITY: f32 = 1.0; @@ -113,12 +116,12 @@ impl fmt::Display for QualityItem { } } -impl str::FromStr for QualityItem { - type Err = crate::error::ParseError; +impl FromStr for QualityItem { + type Err = ParseError; - fn from_str(qitem_str: &str) -> Result, crate::error::ParseError> { + fn from_str(qitem_str: &str) -> Result { if !qitem_str.is_ascii() { - return Err(crate::error::ParseError::Header); + return Err(ParseError::Header); } // Set defaults used if parsing fails. @@ -139,7 +142,7 @@ impl str::FromStr for QualityItem { if parts[0].len() < 2 { // Can't possibly be an attribute since an attribute needs at least a name followed // by an equals sign. And bare identifiers are forbidden. - return Err(crate::error::ParseError::Header); + return Err(ParseError::Header); } let start = &parts[0][0..2]; @@ -148,25 +151,21 @@ impl str::FromStr for QualityItem { let q_val = &parts[0][2..]; if q_val.len() > 5 { // longer than 5 indicates an over-precise q-factor - return Err(crate::error::ParseError::Header); + return Err(ParseError::Header); } - let q_value = q_val - .parse::() - .map_err(|_| crate::error::ParseError::Header)?; + let q_value = q_val.parse::().map_err(|_| ParseError::Header)?; if (0f32..=1f32).contains(&q_value) { quality = q_value; raw_item = parts[1]; } else { - return Err(crate::error::ParseError::Header); + return Err(ParseError::Header); } } } - let item = raw_item - .parse::() - .map_err(|_| crate::error::ParseError::Header)?; + let item = raw_item.parse::().map_err(|_| ParseError::Header)?; // we already checked above that the quality is within range Ok(QualityItem::new(item, Quality::from_f32(quality))) @@ -224,7 +223,7 @@ mod tests { } } - impl str::FromStr for Encoding { + impl FromStr for Encoding { type Err = crate::error::ParseError; fn from_str(s: &str) -> Result { use Encoding::*; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index a9128bc47..0e61a8e7e 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -2,10 +2,10 @@ use std::{ cmp, + convert::TryFrom, future::Future, marker::PhantomData, pin::Pin, - str::FromStr, task::{Context, Poll}, }; @@ -13,16 +13,18 @@ use actix_http::{ body::{MessageBody, ResponseBody}, encoding::Encoder, http::header::{ContentEncoding, ACCEPT_ENCODING}, + StatusCode, }; use actix_service::{Service, Transform}; -use actix_utils::future::{ok, Ready}; +use actix_utils::future::{ok, Either, Ready}; use futures_core::ready; +use once_cell::sync::Lazy; use pin_project::pin_project; use crate::{ dev::BodyEncoding, service::{ServiceRequest, ServiceResponse}, - Error, + Error, HttpResponse, }; /// Middleware for compressing response payloads. @@ -78,34 +80,78 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } +static SUPPORTED_ALGORITHM_NAMES: Lazy = Lazy::new(|| { + let mut encoding = vec![]; + + #[cfg(feature = "compress-brotli")] + { + encoding.push("br"); + } + + #[cfg(feature = "compress-gzip")] + { + encoding.push("gzip"); + encoding.push("deflate"); + } + + #[cfg(feature = "compress-zstd")] + encoding.push("zstd"); + + assert!( + !encoding.is_empty(), + "encoding can not be empty unless __compress feature has been explicitly enabled by itself" + ); + + encoding.join(", ") +}); + impl Service for CompressMiddleware where - B: MessageBody, S: Service, Error = Error>, + B: MessageBody, { type Response = ServiceResponse>>; type Error = Error; - type Future = CompressResponse; + type Future = Either, Ready>>; actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] 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() { - AcceptEncoding::parse(enc, self.encoding) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - }; + let encoding_result = req + .headers() + .get(&ACCEPT_ENCODING) + .and_then(|val| val.to_str().ok()) + .map(|enc| AcceptEncoding::try_parse(enc, self.encoding)); - CompressResponse { - encoding, - fut: self.service.call(req), - _phantom: PhantomData, + match encoding_result { + // Missing header => fallback to identity + None => Either::left(CompressResponse { + encoding: ContentEncoding::Identity, + fut: self.service.call(req), + _phantom: PhantomData, + }), + + // Valid encoding + Some(Ok(encoding)) => Either::left(CompressResponse { + encoding, + fut: self.service.call(req), + _phantom: PhantomData, + }), + + // There is an HTTP header but we cannot match what client as asked for + Some(Err(_)) => { + let res = HttpResponse::with_body( + StatusCode::NOT_ACCEPTABLE, + SUPPORTED_ALGORITHM_NAMES.as_str(), + ); + let enc = ContentEncoding::Identity; + + Either::right(ok(req.into_response(res.map_body(move |head, body| { + Encoder::response(enc, head, ResponseBody::Other(body.into())) + })))) + } } } } @@ -114,7 +160,6 @@ where pub struct CompressResponse where S: Service, - B: MessageBody, { #[pin] fut: S::Future, @@ -151,6 +196,7 @@ where struct AcceptEncoding { encoding: ContentEncoding, + // TODO: use Quality or QualityItem quality: f64, } @@ -177,26 +223,56 @@ impl PartialOrd for AcceptEncoding { impl PartialEq for AcceptEncoding { fn eq(&self, other: &AcceptEncoding) -> bool { - self.quality == other.quality + self.encoding == other.encoding && self.quality == other.quality } } +/// Parse q-factor from quality strings. +/// +/// If parse fail, then fallback to default value which is 1. +/// More details available here: +fn parse_quality(parts: &[&str]) -> f64 { + for part in parts { + if part.trim().starts_with("q=") { + return part[2..].parse().unwrap_or(1.0); + } + } + + 1.0 +} + +#[derive(Debug, PartialEq, Eq)] +enum AcceptEncodingError { + /// This error occurs when client only support compressed response and server do not have any + /// algorithm that match client accepted algorithms. + CompressionAlgorithmMismatch, +} + impl AcceptEncoding { fn new(tag: &str) -> Option { let parts: Vec<&str> = tag.split(';').collect(); let encoding = match parts.len() { 0 => return None, - _ => ContentEncoding::from(parts[0]), - }; - let quality = match parts.len() { - 1 => encoding.quality(), - _ => f64::from_str(parts[1]).unwrap_or(0.0), + _ => match ContentEncoding::try_from(parts[0]) { + Err(_) => return None, + Ok(x) => x, + }, }; + + let quality = parse_quality(&parts[1..]); + if quality <= 0.0 || quality > 1.0 { + return None; + } + Some(AcceptEncoding { encoding, quality }) } - /// Parse a raw Accept-Encoding header value into an ordered list. - pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { + /// Parse a raw Accept-Encoding header value into an ordered list then return the best match + /// based on middleware configuration. + pub fn try_parse( + raw: &str, + encoding: ContentEncoding, + ) -> Result { let mut encodings = raw .replace(' ', "") .split(',') @@ -206,13 +282,90 @@ impl AcceptEncoding { encodings.sort(); for enc in encodings { - if encoding == ContentEncoding::Auto { - return enc.encoding; - } else if encoding == enc.encoding { - return encoding; + if encoding == ContentEncoding::Auto || encoding == enc.encoding { + return Ok(enc.encoding); } } - ContentEncoding::Identity + // Special case if user cannot accept uncompressed data. + // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding + // TODO: account for whitespace + if raw.contains("*;q=0") || raw.contains("identity;q=0") { + return Err(AcceptEncodingError::CompressionAlgorithmMismatch); + } + + Ok(ContentEncoding::Identity) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! assert_parse_eq { + ($raw:expr, $result:expr) => { + assert_eq!( + AcceptEncoding::try_parse($raw, ContentEncoding::Auto), + Ok($result) + ); + }; + } + + macro_rules! assert_parse_fail { + ($raw:expr) => { + assert!(AcceptEncoding::try_parse($raw, ContentEncoding::Auto).is_err()); + }; + } + + #[test] + fn test_parse_encoding() { + // Test simple case + assert_parse_eq!("br", ContentEncoding::Br); + assert_parse_eq!("gzip", ContentEncoding::Gzip); + assert_parse_eq!("deflate", ContentEncoding::Deflate); + assert_parse_eq!("zstd", ContentEncoding::Zstd); + + // Test space, trim, missing values + assert_parse_eq!("br,,,,", ContentEncoding::Br); + assert_parse_eq!("gzip , br, zstd", ContentEncoding::Gzip); + + // Test float number parsing + assert_parse_eq!("br;q=1 ,", ContentEncoding::Br); + assert_parse_eq!("br;q=1.0 , br", ContentEncoding::Br); + + // Test wildcard + assert_parse_eq!("*", ContentEncoding::Identity); + assert_parse_eq!("*;q=1.0", ContentEncoding::Identity); + } + + #[test] + fn test_parse_encoding_qfactor_ordering() { + assert_parse_eq!("gzip, br, zstd", ContentEncoding::Gzip); + assert_parse_eq!("zstd, br, gzip", ContentEncoding::Zstd); + + assert_parse_eq!("gzip;q=0.4, br;q=0.6", ContentEncoding::Br); + assert_parse_eq!("gzip;q=0.8, br;q=0.4", ContentEncoding::Gzip); + } + + #[test] + fn test_parse_encoding_qfactor_invalid() { + // Out of range + assert_parse_eq!("gzip;q=-5.0", ContentEncoding::Identity); + assert_parse_eq!("gzip;q=5.0", ContentEncoding::Identity); + + // Disabled + assert_parse_eq!("gzip;q=0", ContentEncoding::Identity); + } + + #[test] + fn test_parse_compression_required() { + // Check we fallback to identity if there is an unsupported compression algorithm + assert_parse_eq!("compress", ContentEncoding::Identity); + + // User do not want any compression + assert_parse_fail!("compress, identity;q=0"); + assert_parse_fail!("compress, identity;q=0.0"); + assert_parse_fail!("compress, *;q=0"); + assert_parse_fail!("compress, *;q=0.0"); } } diff --git a/tests/test_server.rs b/tests/test_server.rs index afea39dd9..beb8ff0f5 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1077,3 +1077,22 @@ async fn test_data_drop() { assert_eq!(num.load(Ordering::SeqCst), 0); } + +#[actix_rt::test] +async fn test_accept_encoding_no_match() { + let srv = actix_test::start_with(actix_test::config().h1(), || { + App::new() + .wrap(Compress::default()) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().finish()))) + }); + + let response = srv + .get("/") + .append_header((ACCEPT_ENCODING, "compress, identity;q=0")) + .no_decompress() + .send() + .await + .unwrap(); + + assert_eq!(response.status().as_u16(), 406); +} From 93112644d3da17833ea03fc7856329ec2f35ba1c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 1 Sep 2021 09:53:26 +0100 Subject: [PATCH 060/861] non exhaustive content encoding (#2377) --- Cargo.toml | 2 +- actix-http/CHANGES.md | 3 ++ actix-http/Cargo.toml | 2 +- actix-http/src/encoding/decoder.rs | 3 +- .../src/header/shared/content_encoding.rs | 17 ++++------- src/scope.rs | 4 +-- src/test.rs | 2 +- src/types/form.rs | 2 +- src/types/json.rs | 30 ++++--------------- src/types/query.rs | 30 ++++++++----------- 10 files changed, 35 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cee401363..699717b4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" -smallvec = "1.6" +smallvec = "1.6.1" socket2 = "0.4.0" time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f4efef54a..65206cf9a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx ### Changed +* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] * Minimum supported Rust version (MSRV) is now 1.51. ### Fixed @@ -12,12 +13,14 @@ [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 [#2344]: https://github.com/actix/actix-web/pull/2344 +[#2377]: https://github.com/actix/actix-web/pull/2377 ## 3.0.0-beta.8 - 2021-08-09 ### Fixed * Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) + ## 3.0.0-beta.8 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 68f980982..54505a215 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,7 +73,7 @@ rand = "0.8" regex = "1.3" serde = "1.0" sha-1 = "0.9" -smallvec = "1.6" +smallvec = "1.6.1" time = { version = "0.2.23", default-features = false, features = ["std"] } tokio = { version = "1.2", features = ["sync"] } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 81e97d916..c32983fc7 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,7 +1,6 @@ //! Stream decoders. use std::{ - convert::TryFrom, future::Future, io::{self, Write as _}, pin::Pin, @@ -81,7 +80,7 @@ where let encoding = headers .get(&CONTENT_ENCODING) .and_then(|val| val.to_str().ok()) - .and_then(|x| ContentEncoding::try_from(x).ok()) + .and_then(|x| x.parse().ok()) .unwrap_or(ContentEncoding::Identity); Self::new(stream, encoding) diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index 375e8c2fa..1af109c06 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -1,5 +1,6 @@ -use std::{convert::TryFrom, error, fmt, str::FromStr}; +use std::{convert::TryFrom, str::FromStr}; +use derive_more::{Display, Error}; use http::header::InvalidHeaderValue; use crate::{ @@ -11,19 +12,13 @@ use crate::{ /// Error return when a content encoding is unknown. /// /// Example: 'compress' -#[derive(Debug)] +#[derive(Debug, Display, Error)] +#[display(fmt = "unsupported content encoding")] pub struct ContentEncodingParseError; -impl fmt::Display for ContentEncodingParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Unsupported content encoding") - } -} - -impl error::Error for ContentEncodingParseError {} - /// Represents a supported content encoding. -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[non_exhaustive] pub enum ContentEncoding { /// Automatically select encoding based on encoding negotiation. Auto, diff --git a/src/scope.rs b/src/scope.rs index b2edaedab..7d914f581 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -41,9 +41,9 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// fn main() { /// let app = App::new().service( /// web::scope("/{project_id}/") -/// .service(web::resource("/path1").to(|| async { HttpResponse::Ok() })) +/// .service(web::resource("/path1").to(|| async { "OK" })) /// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) -/// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) +/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed))) /// ); /// } /// ``` diff --git a/src/test.rs b/src/test.rs index 634826d19..34dd6f2d3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -56,7 +56,7 @@ pub fn default_service( /// async fn test_init_service() { /// let app = test::init_service( /// App::new() -/// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) +/// .service(web::resource("/test").to(|| async { "OK" })) /// ).await; /// /// // Create request object diff --git a/src/types/form.rs b/src/types/form.rs index c81f73554..2ace0e063 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -30,7 +30,7 @@ use crate::{ /// /// # Extractor /// To extract typed data from a request body, the inner type `T` must implement the -/// [`serde::Deserialize`] trait. +/// [`DeserializeOwned`] trait. /// /// Use [`FormConfig`] to configure extraction process. /// diff --git a/src/types/json.rs b/src/types/json.rs index ab9708c53..8c2f51a68 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -97,19 +97,13 @@ impl ops::DerefMut for Json { } } -impl fmt::Display for Json -where - T: fmt::Display, -{ +impl fmt::Display for Json { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } -impl Serialize for Json -where - T: Serialize, -{ +impl Serialize for Json { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -133,10 +127,7 @@ impl Responder for Json { } /// See [here](#extractor) for example of usage as an extractor. -impl FromRequest for Json -where - T: DeserializeOwned + 'static, -{ +impl FromRequest for Json { type Error = Error; type Future = JsonExtractFut; type Config = JsonConfig; @@ -166,10 +157,7 @@ pub struct JsonExtractFut { err_handler: JsonErrorHandler, } -impl Future for JsonExtractFut -where - T: DeserializeOwned + 'static, -{ +impl Future for JsonExtractFut { type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -311,10 +299,7 @@ pub enum JsonBody { impl Unpin for JsonBody {} -impl JsonBody -where - T: DeserializeOwned + 'static, -{ +impl JsonBody { /// Create a new future to decode a JSON request payload. #[allow(clippy::borrow_interior_mutable_const)] pub fn new( @@ -395,10 +380,7 @@ where } } -impl Future for JsonBody -where - T: DeserializeOwned + 'static, -{ +impl Future for JsonBody { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/src/types/query.rs b/src/types/query.rs index 1e6f1111f..73d08d092 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -3,14 +3,14 @@ use std::{fmt, ops, sync::Arc}; use actix_utils::future::{err, ok, Ready}; -use serde::de; +use serde::de::DeserializeOwned; use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest}; /// Extract typed information from the request's query. /// /// To extract typed data from the URL query string, the inner type `T` must implement the -/// [`serde::Deserialize`] trait. +/// [`DeserializeOwned`] trait. /// /// Use [`QueryConfig`] to configure extraction process. /// @@ -46,18 +46,18 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ /// // To access the entire underlying query struct, use `.into_inner()`. /// #[get("/debug1")] /// async fn debug1(info: web::Query) -> String { -/// dbg!("Authorization object={:?}", info.into_inner()); +/// dbg!("Authorization object = {:?}", info.into_inner()); /// "OK".to_string() /// } /// -/// // Or use `.0`, which is equivalent to `.into_inner()`. +/// // Or use destructuring, which is equivalent to `.into_inner()`. /// #[get("/debug2")] -/// async fn debug2(info: web::Query) -> String { -/// dbg!("Authorization object={:?}", info.0); +/// async fn debug2(web::Query(info): web::Query) -> String { +/// dbg!("Authorization object = {:?}", info); /// "OK".to_string() /// } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Query(pub T); impl Query { @@ -65,8 +65,10 @@ impl Query { pub fn into_inner(self) -> T { self.0 } +} - /// Deserialize `T` from a URL encoded query parameter string. +impl Query { + /// Deserialize a `T` from the URL encoded query parameter string. /// /// ``` /// # use std::collections::HashMap; @@ -76,10 +78,7 @@ impl Query { /// assert_eq!(numbers.get("two"), Some(&2)); /// assert!(numbers.get("three").is_none()); /// ``` - pub fn from_query(query_str: &str) -> Result - where - T: de::DeserializeOwned, - { + pub fn from_query(query_str: &str) -> Result { serde_urlencoded::from_str::(query_str) .map(Self) .map_err(QueryPayloadError::Deserialize) @@ -107,10 +106,7 @@ impl fmt::Display for Query { } /// See [here](#usage) for example of usage as an extractor. -impl FromRequest for Query -where - T: de::DeserializeOwned, -{ +impl FromRequest for Query { type Error = Error; type Future = Ready>; type Config = QueryConfig; @@ -165,7 +161,7 @@ where /// let query_cfg = web::QueryConfig::default() /// // use custom error handler /// .error_handler(|err, req| { -/// error::InternalError::from_response(err, HttpResponse::Conflict().into()).into() +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() /// }); /// /// App::new() From 53ec66caf47592b2bdfbbab2936d7ac727bcf315 Mon Sep 17 00:00:00 2001 From: Omid Rad Date: Wed, 1 Sep 2021 21:16:41 +0200 Subject: [PATCH 061/861] Send headers within the redirect requests. (#2310) --- awc/CHANGES.md | 3 + awc/src/middleware/redirect.rs | 365 +++++++++++++++++++++++++++------ 2 files changed, 303 insertions(+), 65 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 16132be1c..9c6f258aa 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Send headers within the redirect requests. [#2310] +[#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index ae09edf9c..a8c14d549 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -85,10 +85,12 @@ where let max_redirect_times = self.max_redirect_times; // backup the uri and method for reuse schema and authority. - let (uri, method) = match head { - RequestHeadType::Owned(ref head) => (head.uri.clone(), head.method.clone()), + let (uri, method, headers) = match head { + RequestHeadType::Owned(ref head) => { + (head.uri.clone(), head.method.clone(), head.headers.clone()) + } RequestHeadType::Rc(ref head, ..) => { - (head.uri.clone(), head.method.clone()) + (head.uri.clone(), head.method.clone(), head.headers.clone()) } }; @@ -104,6 +106,7 @@ where max_redirect_times, uri: Some(uri), method: Some(method), + headers: Some(headers), body: body_opt, addr, connector: Some(connector), @@ -127,9 +130,10 @@ pin_project_lite::pin_project! { max_redirect_times: u8, uri: Option, method: Option, + headers: Option, body: Option, addr: Option, - connector: Option> + connector: Option>, } } } @@ -148,6 +152,7 @@ where max_redirect_times, uri, method, + headers, body, addr, connector, @@ -156,79 +161,60 @@ where StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER + | StatusCode::TEMPORARY_REDIRECT + | StatusCode::PERMANENT_REDIRECT if *max_redirect_times > 0 => { - let org_uri = uri.take().unwrap(); - // rebuild uri from the location header value. - let uri = rebuild_uri(&res, org_uri)?; + let is_redirect = res.head().status == StatusCode::TEMPORARY_REDIRECT + || res.head().status == StatusCode::PERMANENT_REDIRECT; - // reset method - let method = method.take().unwrap(); - let method = match method { - Method::GET | Method::HEAD => method, - _ => Method::GET, - }; + let prev_uri = uri.take().unwrap(); + + // rebuild uri from the location header value. + let next_uri = build_next_uri(&res, &prev_uri)?; // take ownership of states that could be reused let addr = addr.take(); let connector = connector.take(); - let mut max_redirect_times = *max_redirect_times; - // use a new request head. - let mut head = RequestHead::default(); - head.uri = uri.clone(); - head.method = method.clone(); - - let head = RequestHeadType::Owned(head); - - max_redirect_times -= 1; - - let fut = connector - .as_ref() - .unwrap() - // remove body - .call(ConnectRequest::Client(head, Body::None, addr)); - - self.set(RedirectServiceFuture::Client { - fut, - max_redirect_times, - uri: Some(uri), - method: Some(method), - // body is dropped on 301,302,303 - body: None, - addr, - connector, - }); - - self.poll(cx) - } - StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT - if *max_redirect_times > 0 => - { - let org_uri = uri.take().unwrap(); - // rebuild uri from the location header value. - let uri = rebuild_uri(&res, org_uri)?; - - // try to reuse body - let body = body.take(); - let body_new = match body { - Some(ref bytes) => Body::Bytes(bytes.clone()), - // TODO: should this be Body::Empty or Body::None. - _ => Body::Empty, + // reset method + let method = if is_redirect { + method.take().unwrap() + } else { + let method = method.take().unwrap(); + match method { + Method::GET | Method::HEAD => method, + _ => Method::GET, + } }; - let addr = addr.take(); - let method = method.take().unwrap(); - let connector = connector.take(); - let mut max_redirect_times = *max_redirect_times; + let mut body = body.take(); + let body_new = if is_redirect { + // try to reuse body + match body { + Some(ref bytes) => Body::Bytes(bytes.clone()), + // TODO: should this be Body::Empty or Body::None. + _ => Body::Empty, + } + } else { + body = None; + // remove body + Body::None + }; + + let mut headers = headers.take().unwrap(); + + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); // use a new request head. let mut head = RequestHead::default(); - head.uri = uri.clone(); + head.uri = next_uri.clone(); head.method = method.clone(); + head.headers = headers.clone(); let head = RequestHeadType::Owned(head); + let mut max_redirect_times = *max_redirect_times; max_redirect_times -= 1; let fut = connector @@ -239,8 +225,9 @@ where self.set(RedirectServiceFuture::Client { fut, max_redirect_times, - uri: Some(uri), + uri: Some(next_uri), method: Some(method), + headers: Some(headers), body, addr, connector, @@ -256,7 +243,7 @@ where } } -fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result { +fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result { let uri = res .headers() .get(header::LOCATION) @@ -266,8 +253,8 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result(uri) @@ -281,12 +268,25 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result HttpResponse { + HttpResponse::TemporaryRedirect() + .append_header(("location", "/test")) + .finish() + } + + async fn test(req: HttpRequest, body: Bytes) -> HttpResponse { + if req.method() == Method::POST && !body.is_empty() { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + App::new() + .service(web::resource("/").route(web::to(root))) + .service(web::resource("/test").route(web::to(test))) + }); + + let res = srv.post("/").send_body("Hello").await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + } + + #[actix_rt::test] + async fn test_redirect_status_kind_301_302_303() { + let srv = actix_test::start(|| { + async fn root() -> HttpResponse { + HttpResponse::Found() + .append_header(("location", "/test")) + .finish() + } + + async fn test(req: HttpRequest, body: Bytes) -> HttpResponse { + if (req.method() == Method::GET || req.method() == Method::HEAD) + && body.is_empty() + { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + App::new() + .service(web::resource("/").route(web::to(root))) + .service(web::resource("/test").route(web::to(test))) + }); + + let res = srv.post("/").send_body("Hello").await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + + let res = srv.post("/").send().await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + } + + #[actix_rt::test] + async fn test_redirect_headers() { + let srv = actix_test::start(|| { + async fn root(req: HttpRequest) -> HttpResponse { + if req + .headers() + .get("custom") + .unwrap_or(&HeaderValue::from_str("").unwrap()) + == "value" + { + HttpResponse::Found() + .append_header(("location", "/test")) + .finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + async fn test(req: HttpRequest) -> HttpResponse { + if req + .headers() + .get("custom") + .unwrap_or(&HeaderValue::from_str("").unwrap()) + == "value" + { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + App::new() + .service(web::resource("/").route(web::to(root))) + .service(web::resource("/test").route(web::to(test))) + }); + + let client = ClientBuilder::new() + .header("custom", "value") + .disable_redirects() + .finish(); + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status().as_u16(), 302); + + let client = ClientBuilder::new().header("custom", "value").finish(); + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + + let client = ClientBuilder::new().finish(); + let res = client + .get(srv.url("/")) + .insert_header(("custom", "value")) + .send() + .await + .unwrap(); + assert_eq!(res.status().as_u16(), 200); + } + + #[actix_rt::test] + async fn test_redirect_cross_origin_headers() { + // defining two services to have two different origins + let srv2 = actix_test::start(|| { + async fn root(req: HttpRequest) -> HttpResponse { + if req.headers().get(header::AUTHORIZATION).is_none() { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + App::new().service(web::resource("/").route(web::to(root))) + }); + let srv2_port: u16 = srv2.addr().port(); + + let srv1 = actix_test::start(move || { + async fn root(req: HttpRequest) -> HttpResponse { + let port = *req.app_data::().unwrap(); + if req.headers().get(header::AUTHORIZATION).is_some() { + HttpResponse::Found() + .append_header(( + "location", + format!("http://localhost:{}/", port).as_str(), + )) + .finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + async fn test1(req: HttpRequest) -> HttpResponse { + if req.headers().get(header::AUTHORIZATION).is_some() { + HttpResponse::Found() + .append_header(("location", "/test2")) + .finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + async fn test2(req: HttpRequest) -> HttpResponse { + if req.headers().get(header::AUTHORIZATION).is_some() { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + + App::new() + .app_data(srv2_port) + .service(web::resource("/").route(web::to(root))) + .service(web::resource("/test1").route(web::to(test1))) + .service(web::resource("/test2").route(web::to(test2))) + }); + + // send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header + let client = ClientBuilder::new() + .header(header::AUTHORIZATION, "auth_key_value") + .finish(); + let res = client.get(srv1.url("/")).send().await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + + // send a request to same origin, http://srv1/test1 then http://srv1/test2. So it should NOT remove any header + let res = client.get(srv1.url("/test1")).send().await.unwrap(); + assert_eq!(res.status().as_u16(), 200); + } + + #[actix_rt::test] + async fn test_remove_sensitive_headers() { + fn gen_headers() -> header::HeaderMap { + let mut headers = header::HeaderMap::new(); + headers.insert(header::USER_AGENT, HeaderValue::from_str("value").unwrap()); + headers.insert( + header::AUTHORIZATION, + HeaderValue::from_str("value").unwrap(), + ); + headers.insert( + header::PROXY_AUTHORIZATION, + HeaderValue::from_str("value").unwrap(), + ); + headers.insert(header::COOKIE, HeaderValue::from_str("value").unwrap()); + headers + } + + // Same origin + let prev_uri = Uri::from_str("https://host/path1").unwrap(); + let next_uri = Uri::from_str("https://host/path2").unwrap(); + let mut headers = gen_headers(); + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); + assert_eq!(headers.len(), 4); + + // different schema + let prev_uri = Uri::from_str("http://host/").unwrap(); + let next_uri = Uri::from_str("https://host/").unwrap(); + let mut headers = gen_headers(); + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); + assert_eq!(headers.len(), 1); + + // different host + let prev_uri = Uri::from_str("https://host1/").unwrap(); + let next_uri = Uri::from_str("https://host2/").unwrap(); + let mut headers = gen_headers(); + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); + assert_eq!(headers.len(), 1); + + // different port + let prev_uri = Uri::from_str("https://host:12/").unwrap(); + let next_uri = Uri::from_str("https://host:23/").unwrap(); + let mut headers = gen_headers(); + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); + assert_eq!(headers.len(), 1); + + // different everything! + let prev_uri = Uri::from_str("http://host1:12/path1").unwrap(); + let next_uri = Uri::from_str("https://host2:23/path2").unwrap(); + let mut headers = gen_headers(); + remove_sensitive_headers(&mut headers, &prev_uri, &next_uri); + assert_eq!(headers.len(), 1); + } } From d8a0f46f264dd52a8d17a8c97036dcf9fc717cbb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 3 Sep 2021 18:00:43 +0100 Subject: [PATCH 062/861] refactor web module (#2379) --- .cargo/config.toml | 2 +- .github/workflows/ci.yml | 40 ++++- CHANGES.md | 5 +- src/dev.rs | 4 +- src/http/header/content_disposition.rs | 10 +- src/lib.rs | 1 - src/service.rs | 2 +- src/web.rs | 220 +++++++------------------ 8 files changed, 103 insertions(+), 181 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index db47ca46d..f417a7053 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,4 +6,4 @@ ci-min-test = "hack check --workspace --no-default-features --tests --examples" ci-default = "check --workspace --bins --tests --examples" ci-full = "check --workspace --all-features --bins --tests --examples" ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" -ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast -- --nocapture" +ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 221d2fb40..647501579 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,13 +80,6 @@ jobs: command: ci-test args: --skip=test_reading_deflate_encoding_large_random_rustls - - name: doc tests - # due to unknown issue with running doc tests on macOS - if: matrix.target.os == 'ubuntu-latest' - uses: actions-rs/cargo@v1 - timeout-minutes: 40 - with: { command: ci-doctest } - - name: Generate coverage file if: > matrix.target.os == 'ubuntu-latest' @@ -106,5 +99,36 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean cargo-cache + + rustdoc: + name: rustdoc + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Rust (nightly) + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.3.0 + + - name: Install cargo-hack + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + + - name: doc tests + uses: actions-rs/cargo@v1 + timeout-minutes: 40 + with: { command: ci-doctest } diff --git a/CHANGES.md b/CHANGES.md index 217ec4f78..6826be075 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,14 +5,17 @@ * Re-export actix-service `ServiceFactory` in `dev` module. [#2325] ### Changed -* Minimum supported Rust version (MSRV) is now 1.51. * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] +* Move `BaseHttpResponse` to `dev::Response`. [#2379] +* Minimum supported Rust version (MSRV) is now 1.51. ### Fixed * Fix quality parse error in Accept-Encoding header. [#2344] +* Re-export correct type at `web::HttpResponse`. [#2379] [#2325]: https://github.com/actix/actix-web/pull/2325 [#2344]: https://github.com/actix/actix-web/pull/2344 +[#2379]: https://github.com/actix/actix-web/pull/2379 ## 4.0.0-beta.8 - 2021-06-26 diff --git a/src/dev.rs b/src/dev.rs index 0817d902f..be3af86a8 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -18,7 +18,7 @@ pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, S #[cfg(feature = "__compress")] pub use actix_http::encoding::Decoder as Decompress; -pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; +pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; pub use actix_service::{ @@ -26,7 +26,7 @@ pub use actix_service::{ }; use crate::http::header::ContentEncoding; -use actix_http::{Response, ResponseBuilder}; +use actix_http::ResponseBuilder; use actix_router::Patterns; diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 6e75fde92..fdd8a7dac 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/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 +//! "The Content-Disposition Header Field" +//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" +//! "Returning Values from Forms: multipart/form-data" +//! Browser conformance tests at: +//! IANA assignment: use once_cell::sync::Lazy; use regex::Regex; diff --git a/src/lib.rs b/src/lib.rs index e7cf46361..d008fdb7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,6 @@ pub mod test; pub(crate) mod types; pub mod web; -pub use actix_http::Response as BaseHttpResponse; pub use actix_http::{body, HttpMessage}; #[doc(inline)] pub use actix_rt as rt; diff --git a/src/service.rs b/src/service.rs index 48167e5b3..b9fa0e128 100644 --- a/src/service.rs +++ b/src/service.rs @@ -476,7 +476,7 @@ impl WebService { /// Set service name. /// - /// Name is used for url generation. + /// Name is used for URL generation. pub fn name(mut self, name: &str) -> Self { self.name = Some(name.to_string()); self diff --git a/src/web.rs b/src/web.rs index 108ff314f..40d7636cf 100644 --- a/src/web.rs +++ b/src/web.rs @@ -3,44 +3,36 @@ use std::future::Future; use actix_http::http::Method; -pub use actix_http::Response as HttpResponse; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; -use crate::error::BlockingError; -use crate::extract::FromRequest; -use crate::handler::Handler; -use crate::resource::Resource; -use crate::responder::Responder; -use crate::route::Route; -use crate::scope::Scope; -use crate::service::WebService; +use crate::{ + error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, + responder::Responder, route::Route, scope::Scope, service::WebService, +}; pub use crate::config::ServiceConfig; pub use crate::data::Data; pub use crate::request::HttpRequest; pub use crate::request_data::ReqData; +pub use crate::response::HttpResponse; pub use crate::types::*; -/// Create resource for a specific path. +/// Creates a new resource for a specific path. /// -/// Resources may have variable path segments. For example, a -/// resource with the path `/a/{name}/c` would match all incoming -/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. +/// Resources may have dynamic path segments. For example, a resource with the path `/a/{name}/c` +/// would match all incoming requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. /// -/// A variable segment is specified in the form `{identifier}`, -/// where the identifier can be used later in a request handler to -/// access the matched value for that segment. This is done by -/// looking up the identifier in the `Params` object returned by -/// `HttpRequest.match_info()` method. +/// A dynamic segment is specified in the form `{identifier}`, where the identifier can be used +/// later in a request handler to access the matched value for that segment. This is done by looking +/// up the identifier in the `Path` object returned by [`HttpRequest.match_info()`] method. /// /// By default, each segment matches the regular expression `[^{}/]+`. /// /// You can also specify a custom regex in the form `{identifier:regex}`: /// -/// For instance, to route `GET`-requests on any route matching -/// `/users/{userid}/{friend}` and store `userid` and `friend` in -/// the exposed `Params` object: +/// For instance, to route `GET`-requests on any route matching `/users/{userid}/{friend}` and store +/// `userid` and `friend` in the exposed `Path` object: /// /// ``` /// use actix_web::{web, App, HttpResponse}; @@ -55,10 +47,16 @@ pub fn resource(path: T) -> Resource { Resource::new(path) } -/// Configure scope for common root path. +/// Creates scope for common path prefix. /// -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. +/// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic +/// path segments. +/// +/// # Examples +/// In this example, three routes are set up (and will handle any method): +/// * `/{project_id}/path1` +/// * `/{project_id}/path2` +/// * `/{project_id}/path3` /// /// ``` /// use actix_web::{web, App, HttpResponse}; @@ -70,148 +68,50 @@ pub fn resource(path: T) -> Resource { /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) /// ); /// ``` -/// -/// In the above example, three routes get added: -/// * /{project_id}/path1 -/// * /{project_id}/path2 -/// * /{project_id}/path3 -/// pub fn scope(path: &str) -> Scope { Scope::new(path) } -/// Create *route* without configuration. +/// Creates a new un-configured route. pub fn route() -> Route { Route::new() } -/// Create *route* with `GET` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `GET` route gets added: -/// * /{project_id} -/// -pub fn get() -> Route { - method(Method::GET) +macro_rules! method_route { + ($method_fn:ident, $method_const:ident) => { + paste::paste! { + #[doc = "Creates a new route with `" $method_const "` method guard."] + /// + /// # Examples + #[doc = "In this example, one `" $method_const " /{project_id}` route is set up:"] + /// ``` + /// use actix_web::{web, App, HttpResponse}; + /// + /// let app = App::new().service( + /// web::resource("/{project_id}") + #[doc = " .route(web::" $method_fn "().to(|| HttpResponse::Ok()))"] + /// + /// ); + /// ``` + pub fn $method_fn() -> Route { + method(Method::$method_const) + } + } + }; } -/// Create *route* with `POST` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::post().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `POST` route gets added: -/// * /{project_id} -/// -pub fn post() -> Route { - method(Method::POST) -} +method_route!(get, GET); +method_route!(post, POST); +method_route!(put, PUT); +method_route!(patch, PATCH); +method_route!(delete, DELETE); +method_route!(head, HEAD); +method_route!(trace, TRACE); -/// Create *route* with `PUT` method guard. +/// Creates a new route with specified method guard. /// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::put().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `PUT` route gets added: -/// * /{project_id} -/// -pub fn put() -> Route { - method(Method::PUT) -} - -/// Create *route* with `PATCH` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::patch().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `PATCH` route gets added: -/// * /{project_id} -/// -pub fn patch() -> Route { - method(Method::PATCH) -} - -/// Create *route* with `DELETE` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::delete().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `DELETE` route gets added: -/// * /{project_id} -/// -pub fn delete() -> Route { - method(Method::DELETE) -} - -/// Create *route* with `HEAD` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::head().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `HEAD` route gets added: -/// * /{project_id} -/// -pub fn head() -> Route { - method(Method::HEAD) -} - -/// Create *route* with `TRACE` method guard. -/// -/// ``` -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::trace().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `HEAD` route gets added: -/// * /{project_id} -/// -pub fn trace() -> Route { - method(Method::TRACE) -} - -/// Create *route* and add method guard. +/// # Examples +/// In this example, one `GET /{project_id}` route is set up: /// /// ``` /// use actix_web::{web, http, App, HttpResponse}; @@ -221,15 +121,11 @@ pub fn trace() -> Route { /// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok())) /// ); /// ``` -/// -/// In the above example, one `GET` route gets added: -/// * /{project_id} -/// pub fn method(method: Method) -> Route { Route::new().method(method) } -/// Create a new route and add handler. +/// Creates a new any-method route with handler. /// /// ``` /// use actix_web::{web, App, HttpResponse, Responder}; @@ -253,7 +149,7 @@ where Route::new().to(handler) } -/// Create raw service for a specific path. +/// Creates a raw service for a specific path. /// /// ``` /// use actix_web::{dev, web, guard, App, Error, HttpResponse}; @@ -272,8 +168,8 @@ pub fn service(path: T) -> WebService { WebService::new(path) } -/// Execute blocking function on a thread pool, returns future that resolves -/// to result of the function execution. +/// Executes blocking function on a thread pool, returns future that resolves to result of the +/// function execution. pub fn block(f: F) -> impl Future> where F: FnOnce() -> R + Send + 'static, From 1383c7d701c35df45abc425e70dae69d9bab1317 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 8 Sep 2021 17:42:14 +0100 Subject: [PATCH 063/861] speed up ci --- .github/workflows/ci.yml | 2 ++ Cargo.toml | 4 ++++ src/web.rs | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 647501579..1ec034bc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,8 @@ jobs: runs-on: ${{ matrix.target.os }} env: + CI: 1 + CARGO_INCREMENTAL: 0 VCPKGRS_DYNAMIC: 1 steps: diff --git a/Cargo.toml b/Cargo.toml index 699717b4d..05ed2eb2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,10 @@ rcgen = "0.8" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.19.0" } +[profile.dev] +# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. +debug = 0 + [profile.release] lto = true opt-level = 3 diff --git a/src/web.rs b/src/web.rs index 40d7636cf..e9f5c8518 100644 --- a/src/web.rs +++ b/src/web.rs @@ -80,10 +80,10 @@ pub fn route() -> Route { macro_rules! method_route { ($method_fn:ident, $method_const:ident) => { paste::paste! { - #[doc = "Creates a new route with `" $method_const "` method guard."] + #[doc = " Creates a new route with `" $method_const "` method guard."] /// /// # Examples - #[doc = "In this example, one `" $method_const " /{project_id}` route is set up:"] + #[doc = " In this example, one `" $method_const " /{project_id}` route is set up:"] /// ``` /// use actix_web::{web, App, HttpResponse}; /// From 8dd30611faf552108638e7719025cbe5ac3d76e8 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Wed, 8 Sep 2021 19:42:40 -0400 Subject: [PATCH 064/861] accept owned strings in TestRequest::param (#2172) * accept owned strings in TestRequest::param * bump actix-router to 0.4.0 * update changelog Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/test.rs | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6826be075..33898794b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,12 +7,14 @@ ### Changed * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] * Move `BaseHttpResponse` to `dev::Response`. [#2379] +* Enable `TestRequest::param` to accept more than just static strings. [#2172] * Minimum supported Rust version (MSRV) is now 1.51. ### Fixed * Fix quality parse error in Accept-Encoding header. [#2344] * Re-export correct type at `web::HttpResponse`. [#2379] +[#2172]: https://github.com/actix/actix-web/pull/2172 [#2325]: https://github.com/actix/actix-web/pull/2325 [#2344]: https://github.com/actix/actix-web/pull/2344 [#2379]: https://github.com/actix/actix-web/pull/2379 diff --git a/src/test.rs b/src/test.rs index 34dd6f2d3..99e708592 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,6 +1,6 @@ //! Various helpers for Actix applications to use during testing. -use std::{net::SocketAddr, rc::Rc}; +use std::{borrow::Cow, net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ @@ -470,19 +470,31 @@ impl TestRequest { self } - /// Set request path pattern parameter - pub fn param(mut self, name: &'static str, value: &'static str) -> Self { + /// Set request path pattern parameter. + /// + /// # Examples + /// ``` + /// use actix_web::test::TestRequest; + /// + /// let req = TestRequest::default().param("foo", "bar"); + /// let req = TestRequest::default().param("foo".to_owned(), "bar".to_owned()); + /// ``` + pub fn param( + mut self, + name: impl Into>, + value: impl Into>, + ) -> Self { self.path.add_static(name, value); self } - /// Set peer addr + /// Set peer addr. pub fn peer_addr(mut self, addr: SocketAddr) -> Self { self.peer_addr = Some(addr); self } - /// Set request payload + /// Set request payload. pub fn set_payload>(mut self, data: B) -> Self { self.req.set_payload(data); self From ba88d3b4bf1cc3cccfd17d53f907422257e16944 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 9 Sep 2021 01:35:41 +0100 Subject: [PATCH 065/861] prepare actix-web beta.9 releases (#2381) * prepare actix-router release 0.5.0-beta.2 * prepare actix-web-codegen release 0.5.0-beta.4 * prepare actix-http release 3.0.0-beta.10 * prepare awc release 3.0.0-beta.8 * prepare actix-web release 4.0.0-beta.9 * prepare actix-http-test release 3.0.0-beta.6 * prepare actix-test release 0.1.0-beta.4 * prepare actix-files release 0.6.0-beta.7 * prepare actix-multipart release 0.4.0-beta.6 * prepare actix-web-actors release 4.0.0-beta.7 * fix http test version * re-add patch * update router repo url * fix http test readme version --- CHANGES.md | 3 +++ Cargo.toml | 10 +++++----- README.md | 4 ++-- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 4 ++-- actix-http-test/CHANGES.md | 3 +++ actix-http-test/Cargo.toml | 20 ++++++++++---------- actix-http-test/README.md | 4 ++-- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 4 ++-- actix-http/README.md | 4 ++-- actix-multipart/CHANGES.md | 3 +++ actix-multipart/Cargo.toml | 8 +++----- actix-multipart/README.md | 4 ++-- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 4 ++-- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 10 +++++----- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/Cargo.toml | 8 ++++---- actix-web-actors/README.md | 4 ++-- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 6 +++--- actix-web-codegen/README.md | 4 ++-- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 10 +++++----- awc/README.md | 4 ++-- 28 files changed, 91 insertions(+), 62 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 33898794b..398ac477a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.9 - 2021-09-09 ### Added * Re-export actix-service `ServiceFactory` in `dev` module. [#2325] diff --git a/Cargo.toml b/Cargo.toml index 05ed2eb2d..60525f3ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.8" +version = "4.0.0-beta.9" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -69,15 +69,15 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-macros = "0.2.1" -actix-router = "0.5.0-beta.1" +actix-router = "0.5.0-beta.2" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } -actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.9" +actix-web-codegen = "0.5.0-beta.4" +actix-http = "3.0.0-beta.10" ahash = "0.7" bytes = "1" @@ -106,7 +106,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.7", features = ["openssl"] } +awc = { version = "3.0.0-beta.8", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/README.md b/README.md index 33784d66a..13ec3a01a 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web/4.0.0-beta.8) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web/4.0.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.8) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.9)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 533f72291..6d1512c22 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.7 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ef288215b..eccf49a77 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.6" +version = "0.6.0-beta.7" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" keywords = ["actix", "http", "async", "futures"] @@ -15,8 +15,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.8", default-features = false } -actix-http = "3.0.0-beta.8" +actix-web = { version = "4.0.0-beta.9", default-features = false } +actix-http = "3.0.0-beta.10" actix-service = "2.0.0" actix-utils = "3.0.0" @@ -33,5 +33,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.8" +actix-web = "4.0.0-beta.9" actix-test = "0.1.0-beta.3" diff --git a/actix-files/README.md b/actix-files/README.md index 5815ef563..31bbd036f 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.6)](https://docs.rs/actix-files/0.6.0-beta.6) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.7)](https://docs.rs/actix-files/0.6.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.6/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.6) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.7/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.7) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 39b6a3a66..69e96f98d 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.5 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c04b5da49..e7fe7adc0 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" -readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-http-test/" -categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket", +] license = "MIT OR Apache-2.0" -exclude = [".gitignore", ".cargo/config"] edition = "2018" [package.metadata.docs.rs] @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.7", default-features = false } +awc = { version = "3.0.0-beta.8", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.8" +actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.10" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 099fb385d..f75b9c137 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http-test/3.0.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 65206cf9a..775b9e6d5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.10 - 2021-09-09 ### Changed * `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] * Minimum supported Rust version (MSRV) is now 1.51. @@ -16,7 +19,7 @@ [#2377]: https://github.com/actix/actix-web/pull/2377 -## 3.0.0-beta.8 - 2021-08-09 +## 3.0.0-beta.9 - 2021-08-09 ### Fixed * Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 54505a215..0e0da8f43 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.9" +version = "3.0.0-beta.10" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] @@ -86,7 +86,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http/README.md b/actix-http/README.md index c509eaff8..b58b47f5c 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http/3.0.0-beta.9) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http/3.0.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.9) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.10) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 1e768ddf5..c32583f08 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.4.0-beta.6 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 5103407ca..6db81cca9 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,13 +1,11 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.5" +version = "0.4.0-beta.6" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" -readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-multipart" license = "MIT OR Apache-2.0" edition = "2018" @@ -16,7 +14,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.8", default-features = false } +actix-web = { version = "4.0.0-beta.9", default-features = false } actix-utils = "3.0.0" bytes = "1" @@ -31,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.8" +actix-http = "3.0.0-beta.10" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index aed16721c..f3366f50c 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.5)](https://docs.rs/actix-multipart/0.4.0-beta.5) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.6)](https://docs.rs/actix-multipart/0.4.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.6/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 990382512..001903438 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.2 - 2021-09-09 * Introduce `ResourceDef::join`. [#380] * Disallow prefix routes with tail segments. [#379] * Enforce path separators on dynamic prefixes. [#378] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 2a2ce1cc1..e32f0edd6 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-beta.1" +version = "0.5.0-beta.2" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", @@ -8,7 +8,7 @@ authors = [ ] description = "Resource path matching and router" keywords = ["actix", "router", "routing"] -repository = "https://github.com/actix/actix-net.git" +repository = "https://github.com/actix/actix-web.git" license = "MIT OR Apache-2.0" edition = "2018" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index dc76ba3fd..58e05c4b6 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.4 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b732cf744..41d32257c 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.3" +version = "0.1.0-beta.4" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -20,13 +20,13 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0" -actix-http = "3.0.0-beta.8" -actix-http-test = { version = "3.0.0-beta.4", features = [] } +actix-http = "3.0.0-beta.10" +actix-http-test = "3.0.0-beta.5" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.7", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.8", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 084e7b272..2e453063f 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.7 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index fcb5195b8..ef6bd919d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.6" +version = "4.0.0-beta.7" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] @@ -16,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.0" -actix-http = "3.0.0-beta.8" -actix-web = { version = "4.0.0-beta.8", default-features = false } +actix-http = "3.0.0-beta.10" +actix-web = { version = "4.0.0-beta.9", default-features = false } bytes = "1" bytestring = "1" @@ -29,6 +29,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.3" -awc = { version = "3.0.0-beta.7", default-features = false } +awc = { version = "3.0.0-beta.8", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 2858d3f20..a647e4bc9 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.6)](https://docs.rs/actix-web-actors/4.0.0-beta.6) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index f0a56b30f..c154d8af4 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.4 - 2021-09-09 * In routing macros, paths are now validated at compile time. [#2350] * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 66f7acf6d..2ad714f40 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.3" +version = "0.5.0-beta.4" description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" @@ -17,13 +17,13 @@ proc-macro = true quote = "1" syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" -actix-router = "0.5.0-beta.1" +actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.3" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.8" +actix-web = "4.0.0-beta.9" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index e69cfbbe5..268e8b01d 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.3)](https://docs.rs/actix-web-codegen/0.5.0-beta.3) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.4)](https://docs.rs/actix-web-codegen/0.5.0-beta.4) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9c6f258aa..252b62efa 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,11 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.8 - 2021-09-09 ### Changed * Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 + ## 3.0.0-beta.7 - 2021-06-26 ### Changed * Change compression algorithm features flags. [#2250] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 016d3b48b..262c3dce5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.7" +version = "3.0.0-beta.8" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -55,7 +55,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" -actix-http = "3.0.0-beta.8" +actix-http = "3.0.0-beta.10" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -77,9 +77,9 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.8", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.8", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.9", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } diff --git a/awc/README.md b/awc/README.md index fe91383ca..868bc5cae 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.7)](https://docs.rs/awc/3.0.0-beta.7) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.8)](https://docs.rs/awc/3.0.0-beta.8) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.7/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.7) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.8/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.8) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 46699e34299ed14401df8ea8022efe47a83041e0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Sep 2021 00:01:01 +0100 Subject: [PATCH 066/861] remove time dep from actix-http (#2383) --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 1 - actix-http-test/src/lib.rs | 11 ++- actix-http/Cargo.toml | 7 +- actix-http/src/config.rs | 29 +++---- actix-http/src/header/shared/http_date.rs | 82 +++++++++++++++++++ actix-http/src/header/shared/httpdate.rs | 97 ----------------------- actix-http/src/header/shared/mod.rs | 4 +- actix-http/src/lib.rs | 1 - actix-http/src/time_parser.rs | 72 ----------------- actix-http/tests/test_server.rs | 1 + src/middleware/logger.rs | 6 +- 12 files changed, 109 insertions(+), 204 deletions(-) create mode 100644 actix-http/src/header/shared/http_date.rs delete mode 100644 actix-http/src/header/shared/httpdate.rs delete mode 100644 actix-http/src/time_parser.rs diff --git a/Cargo.toml b/Cargo.toml index 60525f3ac..73a52182c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6.1" socket2 = "0.4.0" -time = { version = "0.2.23", default-features = false, features = ["std"] } +time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e7fe7adc0..ee4971a1e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -47,7 +47,6 @@ serde = "1.0" serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 0f126c99a..ec7b46ffb 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -7,8 +7,7 @@ #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; -use std::sync::mpsc; -use std::{net, thread, time}; +use std::{net, sync::mpsc, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; @@ -95,15 +94,15 @@ pub async fn test_server_with_addr>( .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) .ssl(builder.build()) } #[cfg(not(feature = "openssl"))] { Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) } }; diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0e0da8f43..889c91331 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,6 +60,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["alloc h2 = "0.3.1" http = "0.2.2" httparse = "1.5.1" +httpdate = "1.0.1" itoa = "0.4" language-tags = "0.3" local-channel = "0.1" @@ -70,11 +71,8 @@ percent-encoding = "2.1" pin-project = "1.0.0" pin-project-lite = "0.2" rand = "0.8" -regex = "1.3" -serde = "1.0" sha-1 = "0.9" smallvec = "1.6.1" -time = { version = "0.2.23", default-features = false, features = ["std"] } tokio = { version = "1.2", features = ["sync"] } # compression @@ -92,11 +90,12 @@ async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" rcgen = "0.8" +regex = "1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } -webpki = { version = "0.21.0" } +webpki = { version = "0.21" } [[example]] name = "ws" diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 97750ff76..069099b8c 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,18 +1,19 @@ -use std::cell::Cell; -use std::fmt::Write; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, net}; +use std::{ + cell::Cell, + fmt::{self, Write}, + net, + rc::Rc, + time::{Duration, SystemTime}, +}; use actix_rt::{ task::JoinHandle, time::{interval, sleep_until, Instant, Sleep}, }; use bytes::BytesMut; -use time::OffsetDateTime; /// "Sun, 06 Nov 1994 08:49:37 GMT".len() -const DATE_VALUE_LENGTH: usize = 29; +pub(crate) const DATE_VALUE_LENGTH: usize = 29; #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting @@ -206,12 +207,7 @@ impl Date { fn update(&mut self) { self.pos = 0; - write!( - self, - "{}", - OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT") - ) - .unwrap(); + write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); } } @@ -269,11 +265,11 @@ impl DateService { } // TODO: move to a util module for testing all spawn handle drop style tasks. -#[cfg(test)] /// Test Module for checking the drop state of certain async tasks that are spawned /// with `actix_rt::spawn` /// /// The target task must explicitly generate `NotifyOnDrop` when spawn the task +#[cfg(test)] mod notify_on_drop { use std::cell::RefCell; @@ -283,9 +279,8 @@ mod notify_on_drop { /// Check if the spawned task is dropped. /// - /// # Panic: - /// - /// When there was no `NotifyOnDrop` instance on current thread + /// # Panics + /// Panics when there was no `NotifyOnDrop` instance on current thread. pub(crate) fn is_dropped() -> bool { NOTIFY_DROPPED.with(|bool| { bool.borrow() diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs new file mode 100644 index 000000000..3441f90af --- /dev/null +++ b/actix-http/src/header/shared/http_date.rs @@ -0,0 +1,82 @@ +use std::{fmt, io::Write, str::FromStr, time::SystemTime}; + +use bytes::BytesMut; +use http::header::{HeaderValue, InvalidHeaderValue}; + +use crate::{ + config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, + helpers::MutWriter, +}; + +/// A timestamp with HTTP formatting and parsing. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct HttpDate(SystemTime); + +impl FromStr for HttpDate { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match httpdate::parse_http_date(s) { + Ok(sys_time) => Ok(HttpDate(sys_time)), + Err(_) => Err(ParseError::Header), + } + } +} + +impl fmt::Display for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let date_str = httpdate::fmt_http_date(self.0); + f.write_str(&date_str) + } +} + +impl IntoHeaderValue for HttpDate { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH); + let mut wrt = MutWriter(&mut buf); + + // unwrap: date output is known to be well formed and of known length + write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap(); + + HeaderValue::from_maybe_shared(buf.split().freeze()) + } +} + +impl From for HttpDate { + fn from(sys_time: SystemTime) -> HttpDate { + HttpDate(sys_time) + } +} + +impl From for SystemTime { + fn from(HttpDate(sys_time): HttpDate) -> SystemTime { + sys_time + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + + #[test] + fn date_header() { + macro_rules! assert_parsed_date { + ($case:expr, $exp:expr) => { + assert_eq!($case.parse::().unwrap(), $exp); + }; + } + + // 784198117 = SystemTime::from(datetime!(1994-11-07 08:48:37).assume_utc()).duration_since(SystemTime::UNIX_EPOCH)); + let nov_07 = HttpDate(SystemTime::UNIX_EPOCH + Duration::from_secs(784198117)); + + assert_parsed_date!("Mon, 07 Nov 1994 08:48:37 GMT", nov_07); + assert_parsed_date!("Monday, 07-Nov-94 08:48:37 GMT", nov_07); + assert_parsed_date!("Mon Nov 7 08:48:37 1994", nov_07); + + assert!("this-is-no-date".parse::().is_err()); + } +} diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs deleted file mode 100644 index 18278a6d8..000000000 --- a/actix-http/src/header/shared/httpdate.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::{ - fmt, - io::Write, - str::FromStr, - time::{SystemTime, UNIX_EPOCH}, -}; - -use bytes::buf::BufMut; -use bytes::BytesMut; -use http::header::{HeaderValue, InvalidHeaderValue}; -use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; - -use crate::error::ParseError; -use crate::header::IntoHeaderValue; -use crate::time_parser; - -/// A timestamp with HTTP formatting and parsing. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(OffsetDateTime); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time_parser::parse_http_date(s) { - Some(t) => Ok(HttpDate(t.assume_utc())), - None => Err(ParseError::Header), - } - } -} - -impl fmt::Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - HttpDate(PrimitiveDateTime::from(sys).assume_utc()) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValue; - - fn try_into_value(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!( - wrt, - "{}", - self.0 - .to_offset(UtcOffset::UTC) - .format("%a, %d %b %Y %H:%M:%S GMT") - ) - .unwrap(); - HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let dt = date.0; - let epoch = OffsetDateTime::unix_epoch(); - - UNIX_EPOCH + (dt - epoch) - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::{date, time, PrimitiveDateTime}; - - #[test] - fn test_date() { - let nov_07 = HttpDate( - PrimitiveDateTime::new(date!(1994 - 11 - 07), time!(8:48:37)).assume_utc(), - ); - - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - nov_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - nov_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - nov_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index b8f9173f9..274e13146 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -3,12 +3,12 @@ mod charset; mod content_encoding; mod extended; -mod httpdate; +mod http_date; mod quality_item; pub use self::charset::Charset; pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; -pub use self::httpdate::HttpDate; +pub use self::http_date::HttpDate; pub use self::quality_item::{q, qitem, Quality, QualityItem}; pub use language_tags::LanguageTag; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 17ee3ff29..3ad8d095e 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -44,7 +44,6 @@ mod request; mod response; mod response_builder; mod service; -mod time_parser; pub mod error; pub mod h1; diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs deleted file mode 100644 index fd82fd42e..000000000 --- a/actix-http/src/time_parser.rs +++ /dev/null @@ -1,72 +0,0 @@ -use time::{Date, OffsetDateTime, PrimitiveDateTime}; - -/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. -pub(crate) fn parse_http_date(time: &str) -> Option { - try_parse_rfc_1123(time) - .or_else(|| try_parse_rfc_850(time)) - .or_else(|| try_parse_asctime(time)) -} - -/// Attempt to parse a `time` string as a RFC 1123 formatted date time string. -/// -/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT` -fn try_parse_rfc_1123(time: &str) -> Option { - time::parse(time, "%a, %d %b %Y %H:%M:%S").ok() -} - -/// Attempt to parse a `time` string as a RFC 850 formatted date time string. -/// -/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC` -fn try_parse_rfc_850(time: &str) -> Option { - let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?; - - // If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3, - // we consider the year as part of this century if it's within the next 50 years, - // otherwise we consider as part of the previous century. - - let now = OffsetDateTime::now_utc(); - let century_start_year = (now.year() / 100) * 100; - let mut expanded_year = century_start_year + dt.year(); - - if expanded_year > now.year() + 50 { - expanded_year -= 100; - } - - let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?; - Some(PrimitiveDateTime::new(date, dt.time())) -} - -/// Attempt to parse a `time` string using ANSI C's `asctime` format. -/// -/// Eg: `Wed Feb 13 15:46:11 2013` -fn try_parse_asctime(time: &str) -> Option { - time::parse(time, "%a %b %_d %H:%M:%S %Y").ok() -} - -#[cfg(test)] -mod tests { - use time::{date, time}; - - use super::*; - - #[test] - fn test_rfc_850_year_shift() { - let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap(); - assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap(); - assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41))); - } -} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 1e6d0b637..c04aeae00 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -183,6 +183,7 @@ async fn test_chunked_payload() { Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), None => panic!("Failed to find size in HTTP Response: {}", data), }; + size }; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 9574b02f7..961eca496 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -18,7 +18,7 @@ use bytes::Bytes; use futures_core::ready; use log::{debug, warn}; use regex::{Regex, RegexSet}; -use time::OffsetDateTime; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ dev::{BodySize, MessageBody}, @@ -538,7 +538,7 @@ impl FormatText { }; } FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), - FormatText::RequestTime => *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")), + FormatText::RequestTime => *self = FormatText::Str(now.format(&Rfc3339).unwrap()), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -767,7 +767,7 @@ mod tests { Ok(()) }; let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S"))); + assert!(s.contains(&now.format(&Rfc3339).unwrap())); } #[actix_rt::test] From 8ae278cb68eda1e6c4fbd3463b018e0f0fe1c313 Mon Sep 17 00:00:00 2001 From: Arniu Tseng Date: Sat, 11 Sep 2021 08:11:16 +0800 Subject: [PATCH 067/861] Remove `FromRequest::Config` (#2233) Co-authored-by: Jonas Platte Co-authored-by: Igor Aleksanov Co-authored-by: Rob Ede --- CHANGES.md | 3 ++ Cargo.toml | 1 + MIGRATION.md | 2 ++ actix-files/src/path_buf.rs | 1 - actix-multipart/src/extractor.rs | 1 - src/data.rs | 1 - src/extract.rs | 55 ++++++++++++++++++++------------ src/info.rs | 2 -- src/request.rs | 1 - src/request_data.rs | 1 - src/types/either.rs | 1 - src/types/form.rs | 30 +++++++++-------- src/types/header.rs | 1 - src/types/json.rs | 1 - src/types/path.rs | 3 +- src/types/payload.rs | 17 ++++------ src/types/query.rs | 3 +- 17 files changed, 66 insertions(+), 58 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 398ac477a..d8831602d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Asscociated type `FromRequest::Config` was removed. [#2233] +[#2233]: https://github.com/actix/actix-web/pull/2233 ## 4.0.0-beta.9 - 2021-09-09 ### Added diff --git a/Cargo.toml b/Cargo.toml index 73a52182c..dc7e9af3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] +rustdoc-args = ["--cfg", "docsrs"] [lib] name = "actix_web" diff --git a/MIGRATION.md b/MIGRATION.md index 9a70adb95..d53bd7bf8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -11,6 +11,8 @@ Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. +* The `type Config` of `FromRequest` was removed. + * Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default all compression algorithms are enabled. To select algorithm you want to include with `middleware::Compress` use following flags: diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 8a87acd5d..76f589307 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -59,7 +59,6 @@ impl AsRef for PathBufWrap { impl FromRequest for PathBufWrap { type Error = UriSegmentError; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { ready(req.match_info().path().parse()) diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index c87f8cc2d..1ad1f203d 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -33,7 +33,6 @@ use crate::server::Multipart; impl FromRequest for Multipart { type Error = Error; type Future = Ready>; - type Config = (); #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { diff --git a/src/data.rs b/src/data.rs index 174faba37..9d4fe0840 100644 --- a/src/data.rs +++ b/src/data.rs @@ -120,7 +120,6 @@ where } impl FromRequest for Data { - type Config = (); type Error = Error; type Future = Ready>; diff --git a/src/extract.rs b/src/extract.rs index 592f7ab83..39062dd1c 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -13,13 +13,42 @@ use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; -/// Trait implemented by types that can be extracted from request. +/// A type that implements [`FromRequest`] is called an **extractor** and can extract data +/// from the request. Examples of types that implement this trait are [`Json`], [`Form`], [`Path`]. /// -/// Types that implement this trait can be used with `Route` handlers. +/// An extractor can be customized by injecting the corresponding configuration with one of: +/// +/// - [`App::app_data()`](`crate::App::app_data`) +/// - [`Scope::app_data()`](`crate::Scope::app_data`) +/// - [`Resource::app_data()`](`crate::Resource::app_data`) +/// +/// Here are some built-in extractors and their corresponding configuration. +/// Please refer to the respective documentation for details. +/// +/// | Extractor | Configuration | +/// |-------------|-------------------| +/// | [`Json`] | [`JsonConfig`] | +/// | [`Form`] | [`FormConfig`] | +/// | [`Path`] | [`PathConfig`] | +/// | [`Query`] | [`QueryConfig`] | +/// | [`Payload`] | [`PayloadConfig`] | +/// | [`String`] | [`PayloadConfig`] | +/// | [`Bytes`] | [`PayloadConfig`] | +/// +/// [`Json`]: crate::web::Json +/// [`JsonConfig`]: crate::web::JsonConfig +/// [`Form`]: crate::web::Form +/// [`FormConfig`]: crate::web::FormConfig +/// [`Path`]: crate::web::Path +/// [`PathConfig`]: crate::web::PathConfig +/// [`Query`]: crate::web::Query +/// [`QueryConfig`]: crate::web::QueryConfig +/// [`Payload`]: crate::web::Payload +/// [`PayloadConfig`]: crate::web::PayloadConfig +/// [`String`]: FromRequest#impl-FromRequest-for-String +/// [`Bytes`]: crate::web::Bytes#impl-FromRequest +#[cfg_attr(docsrs, doc(alias = "Extractor"))] pub trait FromRequest: Sized { - /// Configuration for this extractor. - type Config: Default + 'static; - /// The associated error which can be returned. type Error: Into; @@ -35,14 +64,6 @@ pub trait FromRequest: Sized { fn extract(req: &HttpRequest) -> Self::Future { Self::from_request(req, &mut Payload::None) } - - /// Create and configure config instance. - fn configure(f: F) -> Self::Config - where - F: FnOnce(Self::Config) -> Self::Config, - { - f(Self::Config::default()) - } } /// Optionally extract a field from the request @@ -65,7 +86,6 @@ pub trait FromRequest: Sized { /// impl FromRequest for Thing { /// type Error = Error; /// type Future = Ready>; -/// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { @@ -100,7 +120,6 @@ where { type Error = Error; type Future = FromRequestOptFuture; - type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -156,7 +175,6 @@ where /// impl FromRequest for Thing { /// type Error = Error; /// type Future = Ready>; -/// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { @@ -189,7 +207,6 @@ where { type Error = Error; type Future = FromRequestResFuture; - type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -233,7 +250,6 @@ where impl FromRequest for Uri { type Error = Infallible; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { ok(req.uri().clone()) @@ -255,7 +271,6 @@ impl FromRequest for Uri { impl FromRequest for Method { type Error = Infallible; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { ok(req.method().clone()) @@ -266,7 +281,6 @@ impl FromRequest for Method { impl FromRequest for () { type Error = Infallible; type Future = Ready>; - type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { ok(()) @@ -306,7 +320,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { type Error = Error; type Future = $fut_type<$($T),+>; - type Config = ($($T::Config),+); fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { $fut_type { diff --git a/src/info.rs b/src/info.rs index de8ad67ee..d928a1e63 100644 --- a/src/info.rs +++ b/src/info.rs @@ -209,7 +209,6 @@ impl ConnectionInfo { impl FromRequest for ConnectionInfo { type Error = Infallible; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { ok(req.connection_info().clone()) @@ -252,7 +251,6 @@ impl ResponseError for MissingPeerAddr {} impl FromRequest for PeerAddr { type Error = MissingPeerAddr; type Future = Ready>; - type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { match req.peer_addr() { diff --git a/src/request.rs b/src/request.rs index c25a5397a..0027f9b4b 100644 --- a/src/request.rs +++ b/src/request.rs @@ -358,7 +358,6 @@ impl Drop for HttpRequest { /// } /// ``` impl FromRequest for HttpRequest { - type Config = (); type Error = Error; type Future = Ready>; diff --git a/src/request_data.rs b/src/request_data.rs index 581943015..575dc1eb3 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -64,7 +64,6 @@ impl Deref for ReqData { } impl FromRequest for ReqData { - type Config = (); type Error = Error; type Future = Ready>; diff --git a/src/types/either.rs b/src/types/either.rs index 35e63cec9..5700b63c7 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -187,7 +187,6 @@ where { type Error = EitherExtractError; type Future = EitherExtractFut; - type Config = (); fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { EitherExtractFut { diff --git a/src/types/form.rs b/src/types/form.rs index 2ace0e063..71100eb97 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -126,20 +126,12 @@ impl FromRequest for Form where T: DeserializeOwned + 'static, { - type Config = FormConfig; type Error = Error; type Future = FormExtractFut; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let (limit, err_handler) = req - .app_data::() - .or_else(|| { - req.app_data::>() - .map(|d| d.as_ref()) - }) - .map(|c| (c.limit, c.err_handler.clone())) - .unwrap_or((16384, None)); + let FormConfig { limit, err_handler } = FormConfig::from_req(req).clone(); FormExtractFut { fut: UrlEncoded::new(req, payload).limit(limit), @@ -241,14 +233,26 @@ impl FormConfig { self.err_handler = Some(Rc::new(f)); self } + + /// Extract payload config from app data. + /// + /// Checks both `T` and `Data`, in that order, and falls back to the default payload config. + fn from_req(req: &HttpRequest) -> &Self { + req.app_data::() + .or_else(|| req.app_data::>().map(|d| d.as_ref())) + .unwrap_or(&DEFAULT_CONFIG) + } } +/// Allow shared refs used as default. +const DEFAULT_CONFIG: FormConfig = FormConfig { + limit: 16_384, // 2^14 bytes (~16kB) + err_handler: None, +}; + impl Default for FormConfig { fn default() -> Self { - FormConfig { - limit: 16_384, // 2^14 bytes (~16kB) - err_handler: None, - } + DEFAULT_CONFIG } } diff --git a/src/types/header.rs b/src/types/header.rs index 9b64f445d..6ea77faf6 100644 --- a/src/types/header.rs +++ b/src/types/header.rs @@ -62,7 +62,6 @@ where { type Error = ParseError; type Future = Ready>; - type Config = (); #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { diff --git a/src/types/json.rs b/src/types/json.rs index 8c2f51a68..19443ea96 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -130,7 +130,6 @@ impl Responder for Json { impl FromRequest for Json { type Error = Error; type Future = JsonExtractFut; - type Config = JsonConfig; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { diff --git a/src/types/path.rs b/src/types/path.rs index 4052646e3..aed897fa9 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -97,12 +97,11 @@ where { type Error = Error; type Future = Ready>; - type Config = PathConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req - .app_data::() + .app_data::() .and_then(|c| c.ehandler.clone()); ready( diff --git a/src/types/payload.rs b/src/types/payload.rs index 188da6201..46ad96beb 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -63,7 +63,6 @@ impl Stream for Payload { /// See [here](#usage) for example of usage as an extractor. impl FromRequest for Payload { - type Config = PayloadConfig; type Error = Error; type Future = Ready>; @@ -90,7 +89,6 @@ impl FromRequest for Payload { /// } /// ``` impl FromRequest for Bytes { - type Config = PayloadConfig; type Error = Error; type Future = Either>>; @@ -126,8 +124,7 @@ impl<'a> Future for BytesExtractFut { /// /// Text extractor automatically decode body according to the request's charset. /// -/// [**PayloadConfig**](PayloadConfig) allows to configure -/// extraction process. +/// Use [`PayloadConfig`] to configure extraction process. /// /// # Examples /// ``` @@ -139,7 +136,6 @@ impl<'a> Future for BytesExtractFut { /// format!("Body {}!", text) /// } impl FromRequest for String { - type Config = PayloadConfig; type Error = Error; type Future = Either>>; @@ -198,14 +194,15 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result fmt::Display for Query { impl FromRequest for Query { type Error = Error; type Future = Ready>; - type Config = QueryConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req - .app_data::() + .app_data::() .and_then(|c| c.err_handler.clone()); serde_urlencoded::from_str::(req.query_string()) From 450ff5fa1dbbf9aa9adb68f711ed5e3b53445bab Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Sep 2021 16:48:47 +0100 Subject: [PATCH 068/861] improve extract docs (#2384) --- CHANGES.md | 5 ++++- src/extract.rs | 28 +++++++++++++++++++--------- src/types/form.rs | 2 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 3 ++- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d8831602d..e2bd6ec8b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,12 @@ ## Unreleased - 2021-xx-xx ### Changed -* Asscociated type `FromRequest::Config` was removed. [#2233] +* Associated type `FromRequest::Config` was removed. [#2233] +* Inner field made private on `web::Payload`. [#????] [#2233]: https://github.com/actix/actix-web/pull/2233 +[#????]: https://github.com/actix/actix-web/pull/???? + ## 4.0.0-beta.9 - 2021-09-09 ### Added diff --git a/src/extract.rs b/src/extract.rs index 39062dd1c..29fd0d05e 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -13,28 +13,37 @@ use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; -/// A type that implements [`FromRequest`] is called an **extractor** and can extract data -/// from the request. Examples of types that implement this trait are [`Json`], [`Form`], [`Path`]. +/// A type that implements [`FromRequest`] is called an **extractor** and can extract data from +/// the request. Some types that implement this trait are: [`Json`], [`Header`], and [`Path`]. /// +/// # Configuration /// An extractor can be customized by injecting the corresponding configuration with one of: /// -/// - [`App::app_data()`](`crate::App::app_data`) -/// - [`Scope::app_data()`](`crate::Scope::app_data`) -/// - [`Resource::app_data()`](`crate::Resource::app_data`) +/// - [`App::app_data()`][crate::App::app_data] +/// - [`Scope::app_data()`][crate::Scope::app_data] +/// - [`Resource::app_data()`][crate::Resource::app_data] /// /// Here are some built-in extractors and their corresponding configuration. /// Please refer to the respective documentation for details. /// /// | Extractor | Configuration | /// |-------------|-------------------| +/// | [`Header`] | _None_ | +/// | [`Path`] | [`PathConfig`] | /// | [`Json`] | [`JsonConfig`] | /// | [`Form`] | [`FormConfig`] | -/// | [`Path`] | [`PathConfig`] | /// | [`Query`] | [`QueryConfig`] | -/// | [`Payload`] | [`PayloadConfig`] | -/// | [`String`] | [`PayloadConfig`] | /// | [`Bytes`] | [`PayloadConfig`] | +/// | [`String`] | [`PayloadConfig`] | +/// | [`Payload`] | [`PayloadConfig`] | /// +/// # Implementing An Extractor +/// To reduce duplicate code in handlers where extracting certain parts of a request has a common +/// structure, you can implement `FromRequest` for your own types. +/// +/// Note that the request payload can only be consumed by one extractor. +/// +/// [`Header`]: crate::web::Header /// [`Json`]: crate::web::Json /// [`JsonConfig`]: crate::web::JsonConfig /// [`Form`]: crate::web::Form @@ -47,7 +56,8 @@ use crate::{dev::Payload, Error, HttpRequest}; /// [`PayloadConfig`]: crate::web::PayloadConfig /// [`String`]: FromRequest#impl-FromRequest-for-String /// [`Bytes`]: crate::web::Bytes#impl-FromRequest -#[cfg_attr(docsrs, doc(alias = "Extractor"))] +/// [`Either`]: crate::web::Either +#[doc(alias = "extract", alias = "extractor")] pub trait FromRequest: Sized { /// The associated error which can be returned. type Error: Into; diff --git a/src/types/form.rs b/src/types/form.rs index 71100eb97..098a864de 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -32,7 +32,7 @@ use crate::{ /// To extract typed data from a request body, the inner type `T` must implement the /// [`DeserializeOwned`] trait. /// -/// Use [`FormConfig`] to configure extraction process. +/// Use [`FormConfig`] to configure extraction options. /// /// ``` /// use actix_web::{post, web}; diff --git a/src/types/json.rs b/src/types/json.rs index 19443ea96..df01fdb34 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -34,7 +34,7 @@ use crate::{ /// To extract typed data from a request body, the inner type `T` must implement the /// [`serde::Deserialize`] trait. /// -/// Use [`JsonConfig`] to configure extraction process. +/// Use [`JsonConfig`] to configure extraction options. /// /// ``` /// use actix_web::{post, web, App}; diff --git a/src/types/path.rs b/src/types/path.rs index aed897fa9..b58aec18d 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -14,7 +14,7 @@ use crate::{ /// Extract typed data from request path segments. /// -/// Use [`PathConfig`] to configure extraction process. +/// Use [`PathConfig`] to configure extraction option. /// /// # Examples /// ``` diff --git a/src/types/payload.rs b/src/types/payload.rs index 46ad96beb..00047e8b1 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -43,10 +43,11 @@ use crate::{ /// Ok(format!("Request Body Bytes:\n{:?}", bytes)) /// } /// ``` -pub struct Payload(pub crate::dev::Payload); +pub struct Payload(crate::dev::Payload); impl Payload { /// Unwrap to inner Payload type. + #[inline] pub fn into_inner(self) -> crate::dev::Payload { self.0 } From efefa0d0ce79e24acc79fb0333369e7ab6d79c41 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 11 Sep 2021 09:27:50 -0700 Subject: [PATCH 069/861] web: add option to not require content type header for Json (#2362) Co-authored-by: Rob Ede --- CHANGES.md | 8 ++++-- src/types/json.rs | 68 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e2bd6ec8b..439dd0540 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Option to allow `Json` extractor to work without a `Content-Length` header present. [#2362] + ### Changed * Associated type `FromRequest::Config` was removed. [#2233] -* Inner field made private on `web::Payload`. [#????] +* Inner field made private on `web::Payload`. [#2384] [#2233]: https://github.com/actix/actix-web/pull/2233 -[#????]: https://github.com/actix/actix-web/pull/???? +[#2362]: https://github.com/actix/actix-web/pull/2362 +[#2384]: https://github.com/actix/actix-web/pull/2384 ## 4.0.0-beta.9 - 2021-09-09 diff --git a/src/types/json.rs b/src/types/json.rs index df01fdb34..6d07fe45a 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -127,7 +127,7 @@ impl Responder for Json { } /// See [here](#extractor) for example of usage as an extractor. -impl FromRequest for Json { +impl FromRequest for Json { type Error = Error; type Future = JsonExtractFut; @@ -136,12 +136,13 @@ impl FromRequest for Json { let config = JsonConfig::from_req(req); let limit = config.limit; - let ctype = config.content_type.as_deref(); + let ctype_required = config.content_type_required; + let ctype_fn = config.content_type.as_deref(); let err_handler = config.err_handler.clone(); JsonExtractFut { req: Some(req.clone()), - fut: JsonBody::new(req, payload, ctype).limit(limit), + fut: JsonBody::new(req, payload, ctype_fn, ctype_required).limit(limit), err_handler, } } @@ -156,7 +157,7 @@ pub struct JsonExtractFut { err_handler: JsonErrorHandler, } -impl Future for JsonExtractFut { +impl Future for JsonExtractFut { type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -224,6 +225,7 @@ pub struct JsonConfig { limit: usize, err_handler: JsonErrorHandler, content_type: Option bool + Send + Sync>>, + content_type_required: bool, } impl JsonConfig { @@ -251,6 +253,12 @@ impl JsonConfig { self } + /// Sets whether or not the request must have a `Content-Type` header to be parsed. + pub fn content_type_required(mut self, content_type_required: bool) -> Self { + self.content_type_required = content_type_required; + self + } + /// Extract payload config from app data. Check both `T` and `Data`, in that order, and fall /// back to the default payload config. fn from_req(req: &HttpRequest) -> &Self { @@ -267,6 +275,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig { limit: DEFAULT_LIMIT, err_handler: None, content_type: None, + content_type_required: true, }; impl Default for JsonConfig { @@ -277,15 +286,18 @@ impl Default for JsonConfig { /// Future that resolves to some `T` when parsed from a JSON payload. /// -/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`]. +/// Can deserialize any type `T` that implements [`Deserialize`][serde::Deserialize]. /// /// Returns error if: -/// - content type is not `application/json` -/// - content length is greater than [limit](JsonBody::limit()) +/// - `Content-Type` is not `application/json` when `ctype_required` (passed to [`new`][Self::new]) +/// is `true`. +/// - `Content-Length` is greater than [limit](JsonBody::limit()). +/// - The payload, when consumed, is not valid JSON. pub enum JsonBody { Error(Option), Body { limit: usize, + /// Length as reported by `Content-Length` header, if present. length: Option, #[cfg(feature = "__compress")] payload: Decompress, @@ -304,18 +316,21 @@ impl JsonBody { pub fn new( req: &HttpRequest, payload: &mut Payload, - ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, + ctype_fn: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, + ctype_required: bool, ) -> Self { // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { + let can_parse_json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - || ctype.map_or(false, |predicate| predicate(mime)) + || ctype_fn.map_or(false, |predicate| predicate(mime)) } else { - false + // if `ctype_required` is false, assume payload is + // json even when content-type header is missing + !ctype_required }; - if !json { + if !can_parse_json { return JsonBody::Error(Some(JsonPayloadError::ContentType)); } @@ -325,7 +340,7 @@ impl JsonBody { .and_then(|l| l.to_str().ok()) .and_then(|s| s.parse::().ok()); - // Notice the content_length is not checked against limit of json config here. + // Notice the content-length is not checked against limit of json config here. // As the internal usage always call JsonBody::limit after JsonBody::new. // And limit check to return an error variant of JsonBody happens there. @@ -379,7 +394,7 @@ impl JsonBody { } } -impl Future for JsonBody { +impl Future for JsonBody { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -562,7 +577,7 @@ mod tests { #[actix_rt::test] async fn test_json_body() { let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; + let json = JsonBody::::new(&req, &mut pl, None, true).await; assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -571,7 +586,7 @@ mod tests { header::HeaderValue::from_static("application/text"), )) .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; + let json = JsonBody::::new(&req, &mut pl, None, true).await; assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -585,7 +600,7 @@ mod tests { )) .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None) + let json = JsonBody::::new(&req, &mut pl, None, true) .limit(100) .await; assert!(json_eq( @@ -604,7 +619,7 @@ mod tests { .set_payload(Bytes::from_static(&[0u8; 1000])) .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None) + let json = JsonBody::::new(&req, &mut pl, None, true) .limit(100) .await; @@ -625,7 +640,7 @@ mod tests { .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; + let json = JsonBody::::new(&req, &mut pl, None, true).await; assert_eq!( json.ok().unwrap(), MyObject { @@ -695,6 +710,21 @@ mod tests { assert!(s.is_err()) } + #[actix_rt::test] + async fn test_json_with_no_content_type() { + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().content_type_required(false)) + .to_http_parts(); + + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_ok()) + } + #[actix_rt::test] async fn test_with_config_in_data_wrapper() { let (req, mut pl) = TestRequest::default() From a3806cde190ec27703576a91b470a6c8706cd5b1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 12 Sep 2021 22:41:08 +0100 Subject: [PATCH 070/861] fix changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 439dd0540..05055a517 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## Unreleased - 2021-xx-xx ### Added -* Option to allow `Json` extractor to work without a `Content-Length` header present. [#2362] +* Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] ### Changed * Associated type `FromRequest::Config` was removed. [#2233] From a6707fb7ee401d248268a8ac28ca96a854ad797a Mon Sep 17 00:00:00 2001 From: Omid Rad Date: Mon, 11 Oct 2021 19:28:09 +0200 Subject: [PATCH 071/861] Remove checked_expr (#2401) --- CHANGES.md | 4 ++++ src/service.rs | 10 ---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 05055a517..848a908a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,9 +8,13 @@ * Associated type `FromRequest::Config` was removed. [#2233] * Inner field made private on `web::Payload`. [#2384] +### Removed +* `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] + [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 [#2384]: https://github.com/actix/actix-web/pull/2384 +[#2401]: https://github.com/actix/actix-web/pull/2401 ## 4.0.0-beta.9 - 2021-09-09 diff --git a/src/service.rs b/src/service.rs index b9fa0e128..515d782d9 100644 --- a/src/service.rs +++ b/src/service.rs @@ -393,16 +393,6 @@ impl ServiceResponse { self.response.headers_mut() } - /// Execute closure and in case of error convert it to response. - pub fn checked_expr(mut self, f: F) -> Result - where - F: FnOnce(&mut Self) -> Result<(), E>, - E: Into, - { - f(&mut self).map_err(Into::into)?; - Ok(self) - } - /// Extract response body pub fn into_body(self) -> B { self.response.into_body() From 99985fc4ecdc694182cbbcbb490f0e3e0f6701c6 Mon Sep 17 00:00:00 2001 From: James Rhodes <30299230+jarhodes314@users.noreply.github.com> Date: Tue, 12 Oct 2021 18:35:33 +0100 Subject: [PATCH 072/861] web: implement `into_inner` for `Data` (#2407) --- CHANGES.md | 1 + src/data.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 848a908a3..2509197fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ ### Changed * Associated type `FromRequest::Config` was removed. [#2233] * Inner field made private on `web::Payload`. [#2384] +* `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403] ### Removed * `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] diff --git a/src/data.rs b/src/data.rs index 9d4fe0840..7e01d3462 100644 --- a/src/data.rs +++ b/src/data.rs @@ -75,7 +75,9 @@ impl Data { pub fn new(state: T) -> Data { Data(Arc::new(state)) } +} +impl Data { /// Get reference to inner app data. pub fn get_ref(&self) -> &T { self.0.as_ref() @@ -304,4 +306,38 @@ mod tests { let data_arc = Data::from(dyn_arc); assert_eq!(data_arc_box.get_num(), data_arc.get_num()) } + + #[actix_rt::test] + async fn test_dyn_data_into_arc() { + trait TestTrait { + fn get_num(&self) -> i32; + } + struct A {} + impl TestTrait for A { + fn get_num(&self) -> i32 { + 42 + } + } + let dyn_arc: Arc = Arc::new(A {}); + let data_arc = Data::from(dyn_arc); + let arc_from_data = data_arc.clone().into_inner(); + assert_eq!(data_arc.get_num(), arc_from_data.get_num()) + } + + #[actix_rt::test] + async fn test_get_ref_from_dyn_data() { + trait TestTrait { + fn get_num(&self) -> i32; + } + struct A {} + impl TestTrait for A { + fn get_num(&self) -> i32 { + 42 + } + } + let dyn_arc: Arc = Arc::new(A {}); + let data_arc = Data::from(dyn_arc); + let ref_data = data_arc.get_ref(); + assert_eq!(data_arc.get_num(), ref_data.get_num()) + } } From 6b3ea4fc619ea0c3951d1beefdb13c131e43fd7e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 14 Oct 2021 18:06:31 +0100 Subject: [PATCH 073/861] copy original route macro input with compile errors (#2410) --- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 1 + actix-web-codegen/src/route.rs | 17 +++++++++++++++-- .../trybuild/route-duplicate-method-fail.stderr | 4 ++-- .../trybuild/route-missing-method-fail.stderr | 4 ++-- .../route-unexpected-method-fail.stderr | 4 ++-- 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index c154d8af4..dae9830aa 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Improve error recovery potential when macro input is invalid. [#2410] + +[#2410]: https://github.com/actix/actix-web/pull/2410 ## 0.5.0-beta.4 - 2021-09-09 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 2ad714f40..b8b346b8e 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,6 +21,7 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" +actix-macros = "0.2.2" actix-test = "0.1.0-beta.3" actix-utils = "3.0.0" actix-web = "4.0.0-beta.9" diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index c2f851a0e..4d4af7eca 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -349,8 +349,21 @@ pub(crate) fn with_method( input: TokenStream, ) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - match Route::new(args, input, method) { + match Route::new(args, input.clone(), method) { Ok(route) => route.into_token_stream().into(), - Err(err) => err.to_compile_error().into(), + // on parse err, make IDEs happy; see fn docs + Err(err) => input_and_compile_error(input, err), } } + +/// Converts the error to a token stream and appends it to the original input. +/// +/// Returning the original input in addition to the error is good for IDEs which can gracefully +/// recover and show more precise errors within the macro body. +/// +/// See for more info. +fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream { + let compile_err = TokenStream::from(err.to_compile_error()); + item.extend(compile_err); + return item; +} diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index abdc895d7..90cff1b1c 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -4,8 +4,8 @@ error: HTTP method defined more than once: `GET` 3 | #[route("/", method="GET", method="GET")] | ^^^^^ -error[E0425]: cannot find value `index` in this scope +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied --> $DIR/route-duplicate-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index 0e16b5e27..c36b090c0 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -6,8 +6,8 @@ error: The #[route(..)] macro requires at least one `method` attribute | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0425]: cannot find value `index` in this scope +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied --> $DIR/route-missing-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index a638a96a6..dda366067 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -4,8 +4,8 @@ error: Unexpected HTTP method: `UNEXPECTED` 3 | #[route("/", method="UNEXPECTED")] | ^^^^^^^^^^^^ -error[E0425]: cannot find value `index` in this scope +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied --> $DIR/route-unexpected-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` From efdf3ab1c3ddc80508d020802c79a175134a30aa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 19 Oct 2021 01:32:58 +0100 Subject: [PATCH 074/861] clippy --- actix-http/src/h1/dispatcher.rs | 14 +++++++------- actix-http/src/lib.rs | 7 +------ actix-http/src/ws/mask.rs | 6 +++--- actix-router/src/resource.rs | 20 ++++++++------------ actix-web-codegen/src/route.rs | 2 +- src/middleware/compress.rs | 2 +- src/types/path.rs | 14 ++++---------- src/types/query.rs | 8 +------- 8 files changed, 26 insertions(+), 47 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index aef765b89..69530ed11 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -303,9 +303,9 @@ where body: &impl MessageBody, ) -> Result { let size = body.size(); - let mut this = self.project(); + let this = self.project(); this.codec - .encode(Message::Item((message, size)), &mut this.write_buf) + .encode(Message::Item((message, size)), this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -425,13 +425,13 @@ where Poll::Ready(Some(Ok(item))) => { this.codec.encode( Message::Chunk(Some(item)), - &mut this.write_buf, + this.write_buf, )?; } Poll::Ready(None) => { this.codec - .encode(Message::Chunk(None), &mut this.write_buf)?; + .encode(Message::Chunk(None), this.write_buf)?; // payload stream finished. // set state to None and handle next message this.state.set(State::None); @@ -460,13 +460,13 @@ where Poll::Ready(Some(Ok(item))) => { this.codec.encode( Message::Chunk(Some(item)), - &mut this.write_buf, + this.write_buf, )?; } Poll::Ready(None) => { this.codec - .encode(Message::Chunk(None), &mut this.write_buf)?; + .encode(Message::Chunk(None), this.write_buf)?; // payload stream finished. // set state to None and handle next message this.state.set(State::None); @@ -592,7 +592,7 @@ where let mut updated = false; let mut this = self.as_mut().project(); loop { - match this.codec.decode(&mut this.read_buf) { + match this.codec.decode(this.read_buf) { Ok(Some(msg)) => { updated = true; this.flags.insert(Flags::STARTED); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 3ad8d095e..42ce4ffe4 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -103,14 +103,9 @@ type ConnectCallback = dyn Fn(&IO, &mut Extensions); /// /// # Implementation Details /// Uses Option to reduce necessary allocations when merging with request extensions. +#[derive(Default)] pub(crate) struct OnConnectData(Option); -impl Default for OnConnectData { - fn default() -> Self { - Self(None) - } -} - impl OnConnectData { /// Construct by calling the on-connect callback with the underlying transport I/O. pub(crate) fn from_io( diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 276ca4a85..11a6ddc32 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -25,8 +25,8 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { // // un aligned prefix and suffix would be mask/unmask per byte. // proper aligned middle slice goes into fast path and operates on 4-byte blocks. - let (mut prefix, words, mut suffix) = unsafe { buf.align_to_mut::() }; - apply_mask_fallback(&mut prefix, mask); + let (prefix, words, suffix) = unsafe { buf.align_to_mut::() }; + apply_mask_fallback(prefix, mask); let head = prefix.len() & 3; let mask_u32 = if head > 0 { if cfg!(target_endian = "big") { @@ -40,7 +40,7 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { for word in words.iter_mut() { *word ^= mask_u32; } - apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes()); + apply_mask_fallback(suffix, mask_u32.to_ne_bytes()); } #[cfg(test)] diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index be54336e9..dcd655350 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -394,9 +394,7 @@ impl ResourceDef { pub fn set_name(&mut self, name: impl Into) { let name = name.into(); - if name.is_empty() { - panic!("resource name should not be empty"); - } + assert!(!name.is_empty(), "resource name should not be empty"); self.name = Some(name) } @@ -978,9 +976,7 @@ impl ResourceDef { let (name, pattern) = match param.find(':') { Some(idx) => { - if tail { - panic!("custom regex is not supported for tail match"); - } + assert!(!tail, "custom regex is not supported for tail match"); let (name, pattern) = param.split_at(idx); (name, &pattern[1..]) @@ -1087,12 +1083,12 @@ impl ResourceDef { re.push_str(&escape(unprocessed)); } - if dyn_segment_count > MAX_DYNAMIC_SEGMENTS { - panic!( - "Only {} dynamic segments are allowed, provided: {}", - MAX_DYNAMIC_SEGMENTS, dyn_segment_count - ); - } + assert!( + dyn_segment_count <= MAX_DYNAMIC_SEGMENTS, + "Only {} dynamic segments are allowed, provided: {}", + MAX_DYNAMIC_SEGMENTS, + dyn_segment_count + ); // Store the pattern in capture group #1 to have context info outside it let mut re = format!("({})", re); diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 4d4af7eca..b18252002 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -365,5 +365,5 @@ pub(crate) fn with_method( fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream { let compile_err = TokenStream::from(err.to_compile_error()); item.extend(compile_err); - return item; + item } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 0e61a8e7e..4854f4beb 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -276,7 +276,7 @@ impl AcceptEncoding { let mut encodings = raw .replace(' ', "") .split(',') - .filter_map(|l| AcceptEncoding::new(l)) + .filter_map(AcceptEncoding::new) .collect::>(); encodings.sort(); diff --git a/src/types/path.rs b/src/types/path.rs index b58aec18d..cd24deb81 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -102,7 +102,7 @@ where fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req .app_data::() - .and_then(|c| c.ehandler.clone()); + .and_then(|c| c.err_handler.clone()); ready( de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) @@ -158,9 +158,9 @@ where /// ); /// } /// ``` -#[derive(Clone)] +#[derive(Clone, Default)] pub struct PathConfig { - ehandler: Option Error + Send + Sync>>, + err_handler: Option Error + Send + Sync>>, } impl PathConfig { @@ -169,17 +169,11 @@ impl PathConfig { where F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static, { - self.ehandler = Some(Arc::new(f)); + self.err_handler = Some(Arc::new(f)); self } } -impl Default for PathConfig { - fn default() -> Self { - PathConfig { ehandler: None } - } -} - #[cfg(test)] mod tests { use actix_router::ResourceDef; diff --git a/src/types/query.rs b/src/types/query.rs index eed337194..ba2034bfc 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -167,7 +167,7 @@ impl FromRequest for Query { /// .app_data(query_cfg) /// .service(index); /// ``` -#[derive(Clone)] +#[derive(Clone, Default)] pub struct QueryConfig { err_handler: Option Error + Send + Sync>>, } @@ -183,12 +183,6 @@ impl QueryConfig { } } -impl Default for QueryConfig { - fn default() -> Self { - QueryConfig { err_handler: None } - } -} - #[cfg(test)] mod tests { use actix_http::http::StatusCode; From ad22cc4e7f57ef11dcfc9706f1e1e747a3475815 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 19 Oct 2021 01:59:28 +0100 Subject: [PATCH 075/861] bump msrv to 1.52.1 --- .github/workflows/ci.yml | 2 +- CHANGES.md | 1 + Cargo.toml | 2 -- README.md | 4 ++-- actix-files/CHANGES.md | 1 + actix-files/README.md | 4 ++-- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 4 ++-- actix-http/CHANGES.md | 1 + actix-http/README.md | 4 ++-- actix-multipart/CHANGES.md | 1 + actix-multipart/README.md | 4 ++-- actix-router/CHANGES.md | 1 + actix-test/CHANGES.md | 1 + actix-web-actors/CHANGES.md | 1 + actix-web-actors/README.md | 4 ++-- actix-web-codegen/CHANGES.md | 1 + actix-web-codegen/README.md | 4 ++-- actix-web-codegen/tests/trybuild.rs | 2 +- awc/README.md | 2 +- clippy.toml | 2 +- src/lib.rs | 2 +- 22 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ec034bc8..e4d713b48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } version: - - 1.51.0 # MSRV + - 1.52.0 # MSRV - stable - nightly diff --git a/CHANGES.md b/CHANGES.md index 2509197fa..1654b0856 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ * Associated type `FromRequest::Config` was removed. [#2233] * Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403] +* Minimum supported Rust version (MSRV) is now 1.52. ### Removed * `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] diff --git a/Cargo.toml b/Cargo.toml index dc7e9af3f..ae47398e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,6 @@ members = [ "actix-test", "actix-router", ] -# enable when MSRV is 1.51+ -# resolver = "2" [features] default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] diff --git a/README.md b/README.md index 13ec3a01a..00e8fa6ce 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web/4.0.0-beta.9) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.9)
@@ -32,7 +32,7 @@ * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://docs.rs/awc/) -* Runs on stable Rust 1.51+ +* Runs on stable Rust 1.52+ ## Documentation diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6d1512c22..8e0a3eecf 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 diff --git a/actix-files/README.md b/actix-files/README.md index 31bbd036f..ed15e3333 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.7)](https://docs.rs/actix-files/0.6.0-beta.7) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.7/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.7) @@ -15,4 +15,4 @@ - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) -- Minimum supported Rust version: 1.51 or later +- Minimum Supported Rust Version (MSRV): 1.52 diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 69e96f98d..98b197bcf 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 3.0.0-beta.5 - 2021-09-09 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index f75b9c137..6bf0d710a 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http-test/3.0.0-beta.5) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.5) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http-test) -- Minimum Supported Rust Version (MSRV): 1.51.0 +- Minimum Supported Rust Version (MSRV): 1.52 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 775b9e6d5..71aa8668d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 3.0.0-beta.10 - 2021-09-09 diff --git a/actix-http/README.md b/actix-http/README.md index b58b47f5c..68a6e0a5d 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http/3.0.0-beta.10) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.10) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http) -- Minimum Supported Rust Version (MSRV): 1.51.0 +- Minimum Supported Rust Version (MSRV): 1.52 ## Example diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index c32583f08..33da6a202 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index f3366f50c..254ef877b 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.6)](https://docs.rs/actix-multipart/0.4.0-beta.6) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.6/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.6) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-multipart) -- Minimum Supported Rust Version (MSRV): 1.51.0 +- Minimum Supported Rust Version (MSRV): 1.52 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 001903438..c2858f2ba 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 58e05c4b6..9c0a9ee81 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.1.0-beta.4 - 2021-09-09 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 2e453063f..e3693f0f6 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +* Minimum supported Rust version (MSRV) is now 1.52. ## 4.0.0-beta.7 - 2021-09-09 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index a647e4bc9..2c29dedf2 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-actors) -- Minimum supported Rust version: 1.51 or later +- Minimum Supported Rust Version (MSRV): 1.52 diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index dae9830aa..4b3e04acd 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx * Improve error recovery potential when macro input is invalid. [#2410] +* Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 268e8b01d..ee552cfb5 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.4)](https://docs.rs/actix-web-codegen/0.5.0-beta.4) -[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) +[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) -- Minimum supported Rust version: 1.51 or later. +- Minimum Supported Rust Version (MSRV): 1.52 ## Compile Testing diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 54bc1caec..edbe1a8ed 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.51)] // MSRV +#[rustversion::stable(1.52)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/awc/README.md b/awc/README.md index 868bc5cae..38c967e69 100644 --- a/awc/README.md +++ b/awc/README.md @@ -12,7 +12,7 @@ - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) -- Minimum Supported Rust Version (MSRV): 1.51.0 +- Minimum Supported Rust Version (MSRV): 1.52 ## Example diff --git a/clippy.toml b/clippy.toml index 829dd1c59..cef91fde7 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.51" +msrv = "1.52" diff --git a/src/lib.rs b/src/lib.rs index d008fdb7f..3ad77ff5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://docs.rs/awc/) -//! * Runs on stable Rust 1.51+ +//! * Runs on stable Rust 1.52+ //! //! # Crate Features //! * `cookies` - cookies support (enabled by default) From 591abc37c31d969998247dda91baf4c950d0cd91 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 19 Oct 2021 17:30:32 +0100 Subject: [PATCH 076/861] add test runtime macro (#2409) --- Cargo.toml | 2 +- actix-files/src/lib.rs | 2 +- actix-files/tests/encoding.rs | 2 +- actix-files/tests/guard.rs | 2 +- actix-http-test/src/lib.rs | 2 +- actix-http/src/client/pool.rs | 1 + actix-http/src/h1/encoder.rs | 1 + actix-router/src/router.rs | 3 +- actix-test/src/lib.rs | 4 +- actix-web-codegen/CHANGES.md | 2 + actix-web-codegen/Cargo.toml | 11 ++--- actix-web-codegen/src/lib.rs | 40 ++++++++++++++----- actix-web-codegen/src/route.rs | 22 +++++----- actix-web-codegen/tests/test_macro.rs | 2 +- actix-web-codegen/tests/trybuild.rs | 2 + .../tests/trybuild/test-runtime.rs | 6 +++ examples/basic.rs | 2 +- src/test.rs | 16 ++++---- tests/test-macro-import-conflict.rs | 15 +++++++ 19 files changed, 94 insertions(+), 43 deletions(-) create mode 100644 actix-web-codegen/tests/trybuild/test-runtime.rs create mode 100644 tests/test-macro-import-conflict.rs diff --git a/Cargo.toml b/Cargo.toml index ae47398e1..630ef5642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" -actix-macros = "0.2.1" +actix-macros = "0.2.3" actix-router = "0.5.0-beta.2" actix-rt = "2.2" actix-server = "2.0.0-beta.3" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 1eb091aaf..175c6eaee 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -83,7 +83,7 @@ mod tests { use super::*; - #[actix_rt::test] + #[actix_web::test] async fn test_file_extension_to_mime() { let m = file_extension_to_mime(""); assert_eq!(m, mime::APPLICATION_OCTET_STREAM); diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index d21d4f8fd..652a7c12b 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -8,7 +8,7 @@ use actix_web::{ App, }; -#[actix_rt::test] +#[actix_web::test] async fn test_utf8_file_contents() { // use default ISO-8859-1 encoding let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; diff --git a/actix-files/tests/guard.rs b/actix-files/tests/guard.rs index 8b1785e7f..d053f3fdc 100644 --- a/actix-files/tests/guard.rs +++ b/actix-files/tests/guard.rs @@ -7,7 +7,7 @@ use actix_web::{ }; use bytes::Bytes; -#[actix_rt::test] +#[actix_web::test] async fn test_guard_filter() { let srv = test::init_service( App::new() diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index ec7b46ffb..c7b083b5e 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -36,7 +36,7 @@ use socket2::{Domain, Protocol, Socket, Type}; /// Ok(HttpResponse::Ok().into()) /// } /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_example() { /// let mut srv = TestServer::start( /// || HttpService::new( diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 88188038f..7c36dcff9 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -186,6 +186,7 @@ where let mut conn = None; // check if there is idle connection for given key. + #[allow(clippy::must_not_suspend)] let mut map = inner.available.borrow_mut(); if let Some(conns) = map.get_mut(&key) { diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 5e1d47785..ead14206b 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -20,6 +20,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; #[derive(Debug)] pub(crate) struct MessageEncoder { + #[allow(dead_code)] pub length: BodySize, pub te: TransferEncoding, _phantom: PhantomData, diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index f5deb8583..fad1a440b 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -6,8 +6,9 @@ use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath}; pub struct ResourceId(pub u16); /// Information about current resource -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub struct ResourceInfo { + #[allow(dead_code)] resource: ResourceId, } diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index c863af44a..23a7eeba1 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -64,7 +64,7 @@ pub use actix_web::test::{ /// Ok(HttpResponse::Ok()) /// } /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_example() { /// let srv = actix_test::start(|| /// App::new().service(my_handler) @@ -104,7 +104,7 @@ where /// Ok(HttpResponse::Ok()) /// } /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_example() { /// let srv = actix_test::start_with(actix_test::config().h1(), || /// App::new().service(my_handler) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 4b3e04acd..f1f050b2c 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,9 +2,11 @@ ## Unreleased - 2021-xx-xx * Improve error recovery potential when macro input is invalid. [#2410] +* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] * Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 +[#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index b8b346b8e..afedafdfd 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -2,11 +2,12 @@ name = "actix-web-codegen" version = "0.5.0-beta.4" description = "Routing and runtime macros for Actix Web" -readme = "README.md" homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web" -documentation = "https://docs.rs/actix-web-codegen" -authors = ["Nikolay Kim "] +repository = "https://github.com/actix/actix-web.git" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] license = "MIT OR Apache-2.0" edition = "2018" @@ -21,7 +22,7 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" -actix-macros = "0.2.2" +actix-macros = "0.2.3" actix-test = "0.1.0-beta.3" actix-utils = "3.0.0" actix-web = "4.0.0-beta.9" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 2237f422c..85faf6bca 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -59,6 +59,7 @@ #![recursion_limit = "512"] use proc_macro::TokenStream; +use quote::quote; mod route; @@ -157,24 +158,41 @@ method_macro! { } /// Marks async main function as the actix system entry-point. -/// -/// # Actix Web Re-export -/// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications. -/// + /// # Examples /// ``` -/// #[actix_web_codegen::main] +/// #[actix_web::main] /// async fn main() { /// async { println!("Hello world"); }.await /// } /// ``` #[proc_macro_attribute] pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { - use quote::quote; - let input = syn::parse_macro_input!(item as syn::ItemFn); - (quote! { - #[actix_web::rt::main(system = "::actix_web::rt::System")] - #input + let mut output: TokenStream = (quote! { + #[::actix_web::rt::main(system = "::actix_web::rt::System")] }) - .into() + .into(); + + output.extend(item); + output +} + +/// Marks async test functions to use the actix system entry-point. +/// +/// # Examples +/// ``` +/// #[actix_web::test] +/// async fn test() { +/// assert_eq!(async { "Hello world" }.await, "Hello world"); +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { + let mut output: TokenStream = (quote! { + #[::actix_web::rt::test(system = "::actix_web::rt::System")] + }) + .into(); + + output.extend(item); + output } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index b18252002..eac1948a7 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -220,7 +220,7 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType { impl Route { pub fn new( args: AttributeArgs, - input: TokenStream, + ast: syn::ItemFn, method: Option, ) -> syn::Result { if args.is_empty() { @@ -234,14 +234,11 @@ impl Route { ), )); } - let ast: syn::ItemFn = syn::parse(input)?; + let name = ast.sig.ident.clone(); - // Try and pull out the doc comments so that we can reapply them to the - // generated struct. - // - // Note that multi line doc comments are converted to multiple doc - // attributes. + // Try and pull out the doc comments so that we can reapply them to the generated struct. + // Note that multi line doc comments are converted to multiple doc attributes. let doc_attributes = ast .attrs .iter() @@ -349,9 +346,16 @@ pub(crate) fn with_method( input: TokenStream, ) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - match Route::new(args, input.clone(), method) { + + let ast = match syn::parse::(input.clone()) { + Ok(ast) => ast, + // on parse error, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(input, err), + }; + + match Route::new(args, ast, method) { Ok(route) => route.into_token_stream().into(), - // on parse err, make IDEs happy; see fn docs + // on macro related error, make IDEs happy; see fn docs Err(err) => input_and_compile_error(input, err), } } diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 6b08c409c..769cf2bc3 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -256,7 +256,7 @@ async fn test_auto_async() { assert!(response.status().is_success()); } -#[actix_rt::test] +#[actix_web::test] async fn test_wrap() { let srv = actix_test::start(|| App::new().service(get_wrap)); diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index edbe1a8ed..dd70cb7ca 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -13,4 +13,6 @@ fn compile_macros() { t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); t.pass("tests/trybuild/docstring-ok.rs"); + + t.pass("tests/trybuild/test-runtime.rs"); } diff --git a/actix-web-codegen/tests/trybuild/test-runtime.rs b/actix-web-codegen/tests/trybuild/test-runtime.rs new file mode 100644 index 000000000..0b901b258 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/test-runtime.rs @@ -0,0 +1,6 @@ +#[actix_web::test] +async fn my_test() { + assert!(async { 1 }.await, 1); +} + +fn main() {} diff --git a/examples/basic.rs b/examples/basic.rs index 796f002e8..d29546129 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -35,7 +35,7 @@ async fn main() -> std::io::Result<()> { ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) }) - .bind("127.0.0.1:8080")? + .bind(("127.0.0.1", 8080))? .workers(1) .run() .await diff --git a/src/test.rs b/src/test.rs index 99e708592..43bf612c6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -52,7 +52,7 @@ pub fn default_service( /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_init_service() { /// let app = test::init_service( /// App::new() @@ -98,7 +98,7 @@ where /// ``` /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_response() { /// let app = test::init_service( /// App::new() @@ -129,7 +129,7 @@ where /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_index() { /// let app = test::init_service( /// App::new().service( @@ -176,7 +176,7 @@ where /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_index() { /// let app = test::init_service( /// App::new().service( @@ -224,7 +224,7 @@ where /// name: String, /// } /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_post_person() { /// let app = test::init_service( /// App::new().service( @@ -296,7 +296,7 @@ where /// name: String /// } /// -/// #[actix_rt::test] +/// #[actix_web::test] /// async fn test_add_person() { /// let app = test::init_service( /// App::new().service( @@ -356,8 +356,8 @@ where /// } /// } /// -/// #[test] -/// fn test_index() { +/// #[actix_web::test] +/// async fn test_index() { /// let req = test::TestRequest::default().insert_header("content-type", "text/plain") /// .to_http_request(); /// diff --git a/tests/test-macro-import-conflict.rs b/tests/test-macro-import-conflict.rs new file mode 100644 index 000000000..0d23bb41d --- /dev/null +++ b/tests/test-macro-import-conflict.rs @@ -0,0 +1,15 @@ +//! Checks that test macro does not cause problems in the presence of imports named "test" that +//! could be either a module with test items or the "test with runtime" macro itself. +//! +//! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output +//! `#[test]` and it got run again and since it was in scope. +//! +//! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`). + +use actix_web::test; + +#[actix_web::test] +async fn test_macro_naming_conflict() { + let _req = test::TestRequest::default(); + assert_eq!(async { 1 }.await, 1); +} From 4f6f0b0137ebe6ff106a0ed7b67cb15c68b29fc8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 02:00:11 +0100 Subject: [PATCH 077/861] chore: Bump rustls to 0.20.0 (#2416) Co-authored-by: Kirill Mironov --- CHANGES.md | 2 + Cargo.toml | 10 +++-- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 11 ++--- actix-http/examples/ws.rs | 23 +++++++---- actix-http/src/client/connector.rs | 13 +++--- actix-http/src/h2/service.rs | 2 +- actix-http/src/service.rs | 7 ++-- actix-http/tests/test_rustls.rs | 58 +++++++++++++++++++++------ actix-test/Cargo.toml | 2 +- awc/CHANGES.md | 3 ++ awc/Cargo.toml | 10 +++-- awc/tests/test_rustls_client.rs | 64 ++++++++++++++++++++++-------- tests/test_server.rs | 24 ++++++----- 15 files changed, 162 insertions(+), 73 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1654b0856..cea963ca1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ * Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403] * Minimum supported Rust version (MSRV) is now 1.52. +* Updated rustls to v0.20. [#2414] ### Removed * `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] @@ -17,6 +18,7 @@ [#2362]: https://github.com/actix/actix-web/pull/2362 [#2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 +[#2414]: https://github.com/actix/actix-web/pull/2414 ## 4.0.0-beta.9 - 2021-09-09 diff --git a/Cargo.toml b/Cargo.toml index 630ef5642..e53f4411b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.6", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.4" actix-http = "3.0.0-beta.10" @@ -111,11 +111,15 @@ brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" flate2 = "1.0.13" -zstd = "0.7" +futures-util = { version = "0.3.7", default-features = false, features = ["std"] } rand = "0.8" rcgen = "0.8" +rustls-pemfile = "0.2" tls-openssl = { package = "openssl", version = "0.10.9" } -tls-rustls = { package = "rustls", version = "0.19.0" } +tls-rustls = { package = "rustls", version = "0.20.0" } +webpki = "0.22" +webpki-roots = "0.22" +zstd = "0.7" [profile.dev] # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index ee4971a1e..2bdd6969d 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,7 +31,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.0" -actix-tls = "3.0.0-beta.5" +actix-tls = "3.0.0-beta.6" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 71aa8668d..3911fc00a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Updated rustls to v0.20. [#2414] * Minimum supported Rust version (MSRV) is now 1.52. +[#2414]: https://github.com/actix/actix-web/pull/2414 + ## 3.0.0-beta.10 - 2021-09-09 ### Changed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 889c91331..d0724ba5f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ actix-service = "2.0.0" actix-codec = "0.4.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } +actix-tls = { version = "3.0.0-beta.6", features = ["accept", "connect"] } ahash = "0.7" base64 = "0.13" @@ -85,17 +85,18 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.6", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" rcgen = "0.8" regex = "1.3" +rustls-pemfile = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tls-openssl = { version = "0.10", package = "openssl" } -tls-rustls = { version = "0.19", package = "rustls" } -webpki = { version = "0.21" } +tls-openssl = { package = "openssl", version = "0.10.9" } +tls-rustls = { package = "rustls", version = "0.20.0" } +webpki = { version = "0.22" } [[example]] name = "ws" diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index d3cedf870..b6be4d2f1 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -85,22 +85,31 @@ impl Stream for Heartbeat { fn tls_config() -> rustls::ServerConfig { use std::io::BufReader; - use rustls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig, - }; + use rustls::{Certificate, PrivateKey}; + use rustls_pemfile::{certs, pkcs8_private_keys}; 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 cert_chain = certs(cert_file) + .unwrap() + .into_iter() + .map(Certificate) + .collect(); let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let mut config = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .unwrap(); + + config.alpn_protocols.push(b"http/1.1".to_vec()); + config.alpn_protocols.push(b"h2".to_vec()); config } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index bd46919e8..bde5e4853 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -313,18 +313,15 @@ where SslConnector::Rustls(tls) => { const H2: &[u8] = b"h2"; - use actix_tls::connect::ssl::rustls::{ - RustlsConnector, Session, TlsStream, - }; + use actix_tls::connect::ssl::rustls::{RustlsConnector, TlsStream}; impl IntoConnectionIo for TcpConnection> { fn into_connection_io(self) -> (Box, Protocol) { let sock = self.into_parts().0; - let h2 = sock - .get_ref() - .1 - .get_alpn_protocol() - .map_or(false, |protos| protos.windows(2).any(|w| w == H2)); + let h2 = + sock.get_ref().1.alpn_protocol().map_or(false, |protos| { + protos.windows(2).any(|w| w == H2) + }); if h2 { (Box::new(sock), Protocol::Http2) } else { diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 09e24045b..32dae8ac3 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -177,7 +177,7 @@ mod rustls { > { let mut protos = vec![b"h2".to_vec()]; protos.extend_from_slice(&config.alpn_protocols); - config.set_protocols(&protos); + config.alpn_protocols = protos; Acceptor::new(config) .map_err(TlsError::Tls) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index afe47bf2d..62c968870 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -263,7 +263,7 @@ mod openssl { mod rustls { use std::io; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream}; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; use actix_tls::accept::TlsError; use super::*; @@ -308,14 +308,13 @@ mod rustls { > { let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; protos.extend_from_slice(&config.alpn_protocols); - config.set_protocols(&protos); + config.alpn_protocols = protos; Acceptor::new(config) .map_err(TlsError::Tls) .map_init_err(|_| panic!()) .and_then(|io: TlsStream| async { - let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() - { + let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { Protocol::Http2 } else { diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index cb7c77ad6..69c7db74d 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -3,7 +3,7 @@ extern crate tls_rustls as rustls; use std::{ - convert::Infallible, + convert::{Infallible, TryFrom}, io::{self, BufReader, Write}, net::{SocketAddr, TcpStream as StdTcpStream}, sync::Arc, @@ -20,16 +20,17 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; +use actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS; use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; use futures_core::Stream; use futures_util::stream::{once, StreamExt as _}; use rustls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig as RustlsServerConfig, Session, + Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore, + ServerConfig as RustlsServerConfig, ServerName, }; -use webpki::DNSNameRef; +use rustls_pemfile::{certs, pkcs8_private_keys}; async fn load_body(mut stream: S) -> Result where @@ -47,13 +48,24 @@ fn tls_config() -> RustlsServerConfig { let cert_file = cert.serialize_pem().unwrap(); let key_file = cert.serialize_private_key_pem(); - let mut config = RustlsServerConfig::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 cert_chain = certs(cert_file) + .unwrap() + .into_iter() + .map(Certificate) + .collect(); let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let mut config = RustlsServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .unwrap(); + + config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec()); + config.alpn_protocols.push(H2_ALPN_PROTOCOL.to_vec()); config } @@ -62,19 +74,39 @@ pub fn get_negotiated_alpn_protocol( addr: SocketAddr, client_alpn_protocol: &[u8], ) -> Option> { - let mut config = rustls::ClientConfig::new(); + let mut root_certs = RootCertStore::empty(); + for cert in TLS_SERVER_ROOTS.0 { + let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( + cert.subject, + cert.spki, + cert.name_constraints, + ); + let certs = vec![cert].into_iter(); + root_certs.add_server_trust_anchors(certs); + } + + let mut config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_certs) + .with_no_client_auth(); + config.alpn_protocols.push(client_alpn_protocol.to_vec()); - let mut sess = rustls::ClientSession::new( - &Arc::new(config), - DNSNameRef::try_from_ascii_str("localhost").unwrap(), - ); + + let mut sess = rustls::ClientConnection::new( + Arc::new(config), + ServerName::try_from("localhost").unwrap(), + ) + .unwrap(); + let mut sock = StdTcpStream::connect(addr).unwrap(); let mut stream = rustls::Stream::new(&mut sess, &mut sock); + // The handshake will fails because the client will not be able to verify the server // certificate, but it doesn't matter here as we are just interested in the negotiated ALPN // protocol let _ = stream.flush(); - sess.get_alpn_protocol().map(|proto| proto.to_vec()) + + sess.alpn_protocol().map(|proto| proto.to_vec()) } #[actix_rt::test] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 41d32257c..62a27e5a5 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,4 +35,4 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } -tls-rustls = { package = "rustls", version = "0.19.0", optional = true } +tls-rustls = { package = "rustls", version = "0.20.0", optional = true } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 252b62efa..49d88e5e8 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Updated rustls to v0.20. [#2414] + +[#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 262c3dce5..967d49602 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,8 +73,8 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +tls-openssl = { package = "openssl", version = "0.10.9", optional = true } +tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.9", features = ["openssl"] } @@ -82,7 +82,7 @@ actix-http = { version = "3.0.0-beta.10", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" -actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.6", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" @@ -90,7 +90,9 @@ env_logger = "0.8" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" -webpki = "0.21" +rustls-pemfile = "0.2" +webpki = "0.22" +webpki-roots = "0.22" [[example]] name = "client" diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index bc811c046..95f2d0616 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -8,6 +8,7 @@ use std::{ atomic::{AtomicUsize, Ordering}, Arc, }, + time::SystemTime, }; use actix_http::HttpService; @@ -15,37 +16,52 @@ use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt}; use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; -use rustls::internal::pemfile::{certs, pkcs8_private_keys}; -use rustls::{ClientConfig, NoClientAuth, ServerConfig}; +use rustls::{ + client::{ServerCertVerified, ServerCertVerifier}, + Certificate, ClientConfig, OwnedTrustAnchor, PrivateKey, RootCertStore, ServerConfig, + ServerName, +}; +use rustls_pemfile::{certs, pkcs8_private_keys}; +use webpki_roots::TLS_SERVER_ROOTS; 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 cert_chain = certs(cert_file) + .unwrap() + .into_iter() + .map(Certificate) + .collect(); let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - config + ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .unwrap() } mod danger { + use super::*; + pub struct NoCertificateVerification; - impl rustls::ServerCertVerifier for NoCertificateVerification { + impl ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, - _ocsp: &[u8], - ) -> Result { - Ok(rustls::ServerCertVerified::assertion()) + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) } } } @@ -73,10 +89,26 @@ async fn test_connection_reuse_h2() { }) .await; - // disable TLS verification - let mut config = ClientConfig::new(); + let mut root_certs = RootCertStore::empty(); + for cert in TLS_SERVER_ROOTS.0 { + let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( + cert.subject, + cert.spki, + cert.name_constraints, + ); + let certs = vec![cert].into_iter(); + root_certs.add_server_trust_anchors(certs); + } + + let mut config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_certs) + .with_no_client_auth(); + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - config.set_protocols(&protos); + config.alpn_protocols = protos; + + // disable TLS verification config .dangerous() .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); diff --git a/tests/test_server.rs b/tests/test_server.rs index beb8ff0f5..ff6f5ae5e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -883,27 +883,31 @@ async fn test_brotli_encoding_large_openssl() { mod plus_rustls { use std::io::BufReader; - use rustls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig as RustlsServerConfig, - }; + use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig}; + use rustls_pemfile::{certs, pkcs8_private_keys}; use super::*; - fn rustls_config() -> RustlsServerConfig { + fn tls_config() -> RustlsServerConfig { 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 = RustlsServerConfig::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 cert_chain = certs(cert_file) + .unwrap() + .into_iter() + .map(Certificate) + .collect(); let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - config + RustlsServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .unwrap() } #[actix_rt::test] @@ -914,7 +918,7 @@ mod plus_rustls { .map(char::from) .collect::(); - let srv = actix_test::start_with(actix_test::config().rustls(rustls_config()), || { + let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() .encoding(actix_web::http::ContentEncoding::Identity) From 37f2bf562559a314b9d2f6846cc359d5ea7cf4dc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 02:06:51 +0100 Subject: [PATCH 078/861] clippy --- actix-http/src/client/pool.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 7c36dcff9..88188038f 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -186,7 +186,6 @@ where let mut conn = None; // check if there is idle connection for given key. - #[allow(clippy::must_not_suspend)] let mut map = inner.available.borrow_mut(); if let Some(conns) = map.get_mut(&key) { From c09ec6af4cb74366108d359357196b0e23b2f94d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 02:27:30 +0100 Subject: [PATCH 079/861] split off coverage ci job --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4d713b48..b5e92bb77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,7 @@ jobs: - name: Generate Cargo.lock uses: actions-rs/cargo@v1 - with: - command: generate-lockfile + with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 @@ -82,32 +81,44 @@ jobs: command: ci-test args: --skip=test_reading_deflate_encoding_large_random_rustls - - name: Generate coverage file - if: > - matrix.target.os == 'ubuntu-latest' - && matrix.version == 'stable' - && github.ref == 'refs/heads/master' - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --out Xml --verbose - - name: Upload to Codecov - if: > - matrix.target.os == 'ubuntu-latest' - && matrix.version == 'stable' - && github.ref == 'refs/heads/master' - uses: codecov/codecov-action@v1 - with: - file: cobertura.xml - - name: Clear the cargo caches run: | cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean cargo-cache + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Generate coverage file + if: github.ref == 'refs/heads/master' + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --out Xml --verbose + - name: Upload to Codecov + if: github.ref == 'refs/heads/master' + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } + + rustdoc: name: rustdoc runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 From 9abe166d52c8cfae53627a9a683291c8569aaa69 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 22:32:05 +0100 Subject: [PATCH 080/861] actix-web beta 10 releases (#2417) --- .cargo/config.toml | 5 ++ .github/workflows/ci.yml | 35 +++++++++++++- CHANGES.md | 7 ++- Cargo.toml | 18 ++++---- README.md | 4 +- actix-files/Cargo.toml | 8 ++-- actix-http-test/Cargo.toml | 8 ++-- actix-http/CHANGES.md | 3 ++ actix-http/Cargo.toml | 18 ++++---- actix-http/README.md | 4 +- actix-http/src/client/connector.rs | 74 +++++++++++++++++------------- actix-http/tests/test_rustls.rs | 15 +----- actix-multipart/Cargo.toml | 4 +- actix-test/CHANGES.md | 6 +++ actix-test/Cargo.toml | 19 ++++++-- actix-web-actors/Cargo.toml | 8 ++-- actix-web-codegen/CHANGES.md | 3 ++ actix-web-codegen/Cargo.toml | 6 +-- actix-web-codegen/README.md | 4 +- awc/CHANGES.md | 3 ++ awc/Cargo.toml | 16 +++---- awc/README.md | 4 +- awc/tests/test_rustls_client.rs | 15 +----- 23 files changed, 169 insertions(+), 118 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index f417a7053..40a513efd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,3 +7,8 @@ ci-default = "check --workspace --bins --tests --examples" ci-full = "check --workspace --all-features --bins --tests --examples" ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" + +ci-feature-powerset-check-no-tls="hack --workspace --feature-powerset --skip=__compress,rustls,openssl check" +ci-feature-powerset-check-rustls="hack --workspace --feature-powerset --features=rustls --skip=__compress,openssl check" +ci-feature-powerset-check-openssl="hack --workspace --feature-powerset --features=openssl --skip=__compress,rustls check" +ci-feature-powerset-check-all="hack --workspace --feature-powerset --skip=__compress check" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5e92bb77..aff0b9348 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: target: - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } + - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - 1.52.0 # MSRV - stable @@ -32,6 +32,8 @@ jobs: - uses: actions/checkout@v2 # install OpenSSL on Windows + # TODO: GitHub actions docs state that OpenSSL is + # already installed on these Windows machines somewhere - name: Set vcpkg root if: matrix.target.triple == 'x86_64-pc-windows-msvc' run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append @@ -86,6 +88,36 @@ jobs: cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean cargo-cache + ci_feature_powerset_check: + name: Verify Feature Combinations + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Install cargo-hack + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + + - name: check feature combinations + # if: github.ref == 'refs/heads/master' + uses: actions-rs/cargo@v1 + with: { command: ci-feature-powerset-check-all } + coverage: name: coverage runs-on: ubuntu-latest @@ -115,7 +147,6 @@ jobs: uses: codecov/codecov-action@v1 with: { file: cobertura.xml } - rustdoc: name: rustdoc runs-on: ubuntu-latest diff --git a/CHANGES.md b/CHANGES.md index cea963ca1..a2b6b14ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,15 +1,19 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.10 - 2021-10-20 ### Added * Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] +* `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed * Associated type `FromRequest::Config` was removed. [#2233] * Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403] -* Minimum supported Rust version (MSRV) is now 1.52. * Updated rustls to v0.20. [#2414] +* Minimum supported Rust version (MSRV) is now 1.52. ### Removed * `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] @@ -18,6 +22,7 @@ [#2362]: https://github.com/actix/actix-web/pull/2362 [#2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 +[#2409]: https://github.com/actix/actix-web/pull/2409 [#2414]: https://github.com/actix/actix-web/pull/2414 diff --git a/Cargo.toml b/Cargo.toml index e53f4411b..152282207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.9" +version = "4.0.0-beta.10" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -11,7 +11,7 @@ categories = [ "web-programming::websocket" ] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web" +repository = "https://github.com/actix/actix-web.git" license = "MIT OR Apache-2.0" edition = "2018" @@ -61,22 +61,22 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] -# Internal (PRIVATE!) features used to aid testing and cheking feature status. +# Internal (PRIVATE!) features used to aid testing and checking feature status. # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] [dependencies] actix-codec = "0.4.0" actix-macros = "0.2.3" -actix-router = "0.5.0-beta.2" actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-tls = { version = "3.0.0-beta.6", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } -actix-web-codegen = "0.5.0-beta.4" -actix-http = "3.0.0-beta.10" +actix-http = "3.0.0-beta.11" +actix-router = "0.5.0-beta.2" +actix-web-codegen = "0.5.0-beta.5" ahash = "0.7" bytes = "1" @@ -105,7 +105,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.8", features = ["openssl"] } +awc = { version = "3.0.0-beta.9", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } @@ -117,8 +117,6 @@ rcgen = "0.8" rustls-pemfile = "0.2" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -webpki = "0.22" -webpki-roots = "0.22" zstd = "0.7" [profile.dev] diff --git a/README.md b/README.md index 00e8fa6ce..25b595361 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web/4.0.0-beta.9) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.10)](https://docs.rs/actix-web/4.0.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.9) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.10)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index eccf49a77..664978776 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -15,8 +15,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.9", default-features = false } -actix-http = "3.0.0-beta.10" +actix-web = { version = "4.0.0-beta.10", default-features = false } +actix-http = "3.0.0-beta.11" actix-service = "2.0.0" actix-utils = "3.0.0" @@ -33,5 +33,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.9" -actix-test = "0.1.0-beta.3" +actix-web = "4.0.0-beta.10" +actix-test = "0.1.0-beta.5" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2bdd6969d..d3fc8a47f 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,11 +31,11 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.0" -actix-tls = "3.0.0-beta.6" +actix-tls = "3.0.0-beta.7" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.8", default-features = false } +awc = { version = "3.0.0-beta.9", default-features = false } base64 = "0.13" bytes = "1" @@ -50,5 +50,5 @@ serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.10" +actix-web = { version = "4.0.0-beta.10", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.11" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3911fc00a..3273847c5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.11 - 2021-10-20 ### Changed * Updated rustls to v0.20. [#2414] * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d0724ba5f..3abf537fa 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "actix-http" -version = "3.0.0-beta.10" +version = "3.0.0-beta.11" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web" -categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] +repository = "https://github.com/actix/actix-web.git" +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket", +] license = "MIT OR Apache-2.0" edition = "2018" @@ -46,7 +49,7 @@ actix-service = "2.0.0" actix-codec = "0.4.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-tls = { version = "3.0.0-beta.6", features = ["accept", "connect"] } +actix-tls = { version = "3.0.0-beta.7", features = ["accept", "connect"] } ahash = "0.7" base64 = "0.13" @@ -85,7 +88,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.6", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" @@ -96,7 +99,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -webpki = { version = "0.22" } [[example]] name = "ws" diff --git a/actix-http/README.md b/actix-http/README.md index 68a6e0a5d..5b1e552fd 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http/3.0.0-beta.10) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.11)](https://docs.rs/actix-http/3.0.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.10) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.11) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index bde5e4853..4314511a3 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -28,18 +28,13 @@ use super::pool::ConnectionPool; use super::Connect; use super::Protocol; -#[cfg(feature = "openssl")] -use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; -#[cfg(feature = "rustls")] -use actix_tls::connect::ssl::rustls::ClientConfig; - enum SslConnector { #[allow(dead_code)] None, #[cfg(feature = "openssl")] - Openssl(OpensslConnector), + Openssl(actix_tls::connect::ssl::openssl::SslConnector), #[cfg(feature = "rustls")] - Rustls(std::sync::Arc), + Rustls(std::sync::Arc), } /// Manages HTTP client network connectivity. @@ -78,10 +73,35 @@ impl Connector<()> { } } - // Build Ssl connector with openssl, based on supplied alpn protocols - #[cfg(feature = "openssl")] + /// Provides an empty TLS connector when no TLS feature is enabled. + #[cfg(not(any(feature = "openssl", feature = "rustls")))] + fn build_ssl(_: Vec>) -> SslConnector { + SslConnector::None + } + + /// Build TLS connector with rustls, based on supplied ALPN protocols + /// + /// Note that if both `openssl` and `rustls` features are enabled, rustls will be used. + #[cfg(feature = "rustls")] fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_tls::connect::ssl::openssl::SslMethod; + use actix_tls::connect::tls::rustls::{webpki_roots_cert_store, ClientConfig}; + + let mut config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(webpki_roots_cert_store()) + .with_no_client_auth(); + + config.alpn_protocols = protocols; + + SslConnector::Rustls(std::sync::Arc::new(config)) + } + + /// Build TLS connector with openssl, based on supplied ALPN protocols + #[cfg(all(feature = "openssl", not(feature = "rustls")))] + fn build_ssl(protocols: Vec>) -> SslConnector { + use actix_tls::connect::tls::openssl::{ + SslConnector as OpensslConnector, SslMethod, + }; use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); @@ -91,28 +111,12 @@ impl Connector<()> { } let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); - let _ = ssl - .set_alpn_protos(&alpn) - .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); + if let Err(err) = ssl.set_alpn_protos(&alpn) { + error!("Can not set ALPN protocol: {:?}", err); + } + SslConnector::Openssl(ssl.build()) } - - // Build Ssl connector with rustls, based on supplied alpn protocols - #[cfg(all(not(feature = "openssl"), feature = "rustls"))] - fn build_ssl(protocols: Vec>) -> SslConnector { - let mut config = ClientConfig::new(); - config.set_protocols(&protocols); - config.root_store.add_server_trust_anchors( - &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, - ); - SslConnector::Rustls(std::sync::Arc::new(config)) - } - - // ssl turned off, provides empty ssl connector - #[cfg(not(any(feature = "openssl", feature = "rustls")))] - fn build_ssl(_: Vec>) -> SslConnector { - SslConnector::None - } } impl Connector { @@ -167,14 +171,20 @@ where #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. - pub fn ssl(mut self, connector: OpensslConnector) -> Self { + pub fn ssl( + mut self, + connector: actix_tls::connect::ssl::openssl::SslConnector, + ) -> Self { self.ssl = SslConnector::Openssl(connector); self } #[cfg(feature = "rustls")] /// Use custom `SslConnector` instance. - pub fn rustls(mut self, connector: std::sync::Arc) -> Self { + pub fn rustls( + mut self, + connector: std::sync::Arc, + ) -> Self { self.ssl = SslConnector::Rustls(connector); self } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 69c7db74d..924ef49ad 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -20,7 +20,7 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; -use actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS; +use actix_tls::connect::tls::rustls::webpki_roots_cert_store; use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; @@ -74,20 +74,9 @@ pub fn get_negotiated_alpn_protocol( addr: SocketAddr, client_alpn_protocol: &[u8], ) -> Option> { - let mut root_certs = RootCertStore::empty(); - for cert in TLS_SERVER_ROOTS.0 { - let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( - cert.subject, - cert.spki, - cert.name_constraints, - ); - let certs = vec![cert].into_iter(); - root_certs.add_server_trust_anchors(certs); - } - let mut config = rustls::ClientConfig::builder() .with_safe_defaults() - .with_root_certificates(root_certs) + .with_root_certificates(webpki_roots_cert_store()) .with_no_client_auth(); config.alpn_protocols.push(client_alpn_protocol.to_vec()); diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6db81cca9..f2b1d08a4 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -14,7 +14,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.9", default-features = false } +actix-web = { version = "4.0.0-beta.10", default-features = false } actix-utils = "3.0.0" bytes = "1" @@ -29,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.10" +actix-http = "3.0.0-beta.11" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 9c0a9ee81..070892581 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.5 - 2021-10-20 +* Updated rustls to v0.20. [#2414] * Minimum supported Rust version (MSRV) is now 1.52. +[#2414]: https://github.com/actix/actix-web/pull/2414 + ## 0.1.0-beta.4 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 62a27e5a5..ede72f219 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,13 +1,22 @@ [package] name = "actix-test" -version = "0.1.0-beta.4" +version = "0.1.0-beta.5" authors = [ "Nikolay Kim ", "Rob Ede ", ] -edition = "2018" description = "Integration testing tools for Actix Web applications" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket", +] license = "MIT OR Apache-2.0" +edition = "2018" [features] default = [] @@ -20,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl"] [dependencies] actix-codec = "0.4.0" -actix-http = "3.0.0-beta.10" +actix-http = "3.0.0-beta.11" actix-http-test = "3.0.0-beta.5" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.10", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.8", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.9", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index ef6bd919d..2d987a131 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.0" -actix-http = "3.0.0-beta.10" -actix-web = { version = "4.0.0-beta.9", default-features = false } +actix-http = "3.0.0-beta.11" +actix-web = { version = "4.0.0-beta.10", default-features = false } bytes = "1" bytestring = "1" @@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.3" +actix-test = "0.1.0-beta.5" -awc = { version = "3.0.0-beta.8", default-features = false } +awc = { version = "3.0.0-beta.9", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index f1f050b2c..3811ef030 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.5 - 2021-10-20 * Improve error recovery potential when macro input is invalid. [#2410] * Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index afedafdfd..c04ca435a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.4" +version = "0.5.0-beta.5" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" @@ -23,9 +23,9 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" actix-macros = "0.2.3" -actix-test = "0.1.0-beta.3" +actix-test = "0.1.0-beta.5" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.9" +actix-web = "4.0.0-beta.10" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index ee552cfb5..2ffd5b31c 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.4)](https://docs.rs/actix-web-codegen/0.5.0-beta.4) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.5)](https://docs.rs/actix-web-codegen/0.5.0-beta.5) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 49d88e5e8..5682a237c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.9 - 2021-10-20 * Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 967d49602..5a8235336 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.8" +version = "3.0.0-beta.9" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -14,7 +14,7 @@ categories = [ "web-programming::websocket", ] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web" +repository = "https://github.com/actix/actix-web.git" license = "MIT OR Apache-2.0" edition = "2018" @@ -55,7 +55,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" -actix-http = "3.0.0-beta.10" +actix-http = "3.0.0-beta.11" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" @@ -77,13 +77,13 @@ tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.9", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.10", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.11", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.3" -actix-tls = { version = "3.0.0-beta.6", features = ["openssl", "rustls"] } -actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.5", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" @@ -91,8 +91,6 @@ flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" rustls-pemfile = "0.2" -webpki = "0.22" -webpki-roots = "0.22" [[example]] name = "client" diff --git a/awc/README.md b/awc/README.md index 38c967e69..67bcb9659 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.8)](https://docs.rs/awc/3.0.0-beta.8) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.9)](https://docs.rs/awc/3.0.0-beta.9) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.8/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.8) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.9/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.9) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 95f2d0616..c075a6090 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -14,6 +14,7 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt}; +use actix_tls::connect::tls::rustls::webpki_roots_cert_store; use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use rustls::{ @@ -22,7 +23,6 @@ use rustls::{ ServerName, }; use rustls_pemfile::{certs, pkcs8_private_keys}; -use webpki_roots::TLS_SERVER_ROOTS; fn tls_config() -> ServerConfig { let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); @@ -89,20 +89,9 @@ async fn test_connection_reuse_h2() { }) .await; - let mut root_certs = RootCertStore::empty(); - for cert in TLS_SERVER_ROOTS.0 { - let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( - cert.subject, - cert.spki, - cert.name_constraints, - ); - let certs = vec![cert].into_iter(); - root_certs.add_server_trust_anchors(certs); - } - let mut config = ClientConfig::builder() .with_safe_defaults() - .with_root_certificates(root_certs) + .with_root_certificates(webpki_roots_cert_store()) .with_no_client_auth(); let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; From 4af414064baa2debc963ee00a01e2019a859cf3a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 23:31:46 +0100 Subject: [PATCH 081/861] prepare actix-multipart release 0.4.0-beta.7 --- CHANGES.md | 3 ++- actix-multipart/CHANGES.md | 3 +++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a2b6b14ba..9d7b3180d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,12 +16,13 @@ * Minimum supported Rust version (MSRV) is now 1.52. ### Removed -* `ServiceResponse::checked_expr` was a legacy and just removed. [#2401] +* Useless `ServiceResponse::checked_expr` method. [#2401] [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 [#2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 +[#2403]: https://github.com/actix/actix-web/pull/2403 [#2409]: https://github.com/actix/actix-web/pull/2409 [#2414]: https://github.com/actix/actix-web/pull/2414 diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 33da6a202..09cc707be 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.4.0-beta.7 - 2021-10-20 * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index f2b1d08a4..92637cef9 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.6" +version = "0.4.0-beta.7" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 254ef877b..674814294 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.6)](https://docs.rs/actix-multipart/0.4.0-beta.6) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.7)](https://docs.rs/actix-multipart/0.4.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.6/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.6) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.7/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.7) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From c79b9a0df3ba7690271f0efd5f5f35a97813480b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 20 Oct 2021 23:32:46 +0100 Subject: [PATCH 082/861] prepare actix-files release 0.6.0-beta.8 --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 8e0a3eecf..e1a2c90c5 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.8 - 2021-10-20 * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 664978776..3d7340607 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.7" +version = "0.6.0-beta.8" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" keywords = ["actix", "http", "async", "futures"] diff --git a/actix-files/README.md b/actix-files/README.md index ed15e3333..eac7339ab 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.7)](https://docs.rs/actix-files/0.6.0-beta.7) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.8)](https://docs.rs/actix-files/0.6.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.7/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.7) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.8/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.8) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From d40b6748bc84a98483657a9422493bc16db760aa Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 22 Oct 2021 07:22:58 +0800 Subject: [PATCH 083/861] remove dead dep (#2420) --- actix-http/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3abf537fa..a8fc4255f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -67,7 +67,6 @@ httpdate = "1.0.1" itoa = "0.4" language-tags = "0.3" local-channel = "0.1" -once_cell = "1.5" log = "0.4" mime = "0.3" percent-encoding = "2.1" From d13854505feae2fc1c2435563e209b991bdb4399 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 26 Oct 2021 07:37:40 +0800 Subject: [PATCH 084/861] move actix_http::client module to awc (#2425) --- actix-http/CHANGES.md | 5 + actix-http/Cargo.toml | 7 +- actix-http/src/lib.rs | 1 - actix-test/Cargo.toml | 4 +- awc/Cargo.toml | 19 +- awc/src/builder.rs | 6 +- {actix-http => awc}/src/client/config.rs | 3 +- {actix-http => awc}/src/client/connection.rs | 50 ++--- {actix-http => awc}/src/client/connector.rs | 181 +++++++++---------- {actix-http => awc}/src/client/error.rs | 7 +- {actix-http => awc}/src/client/h1proto.rs | 35 ++-- {actix-http => awc}/src/client/h2proto.rs | 15 +- {actix-http => awc}/src/client/mod.rs | 1 - {actix-http => awc}/src/client/pool.rs | 46 ++--- awc/src/connect.rs | 10 +- awc/src/error.rs | 14 +- awc/src/lib.rs | 32 ++-- awc/src/middleware/redirect.rs | 2 +- 18 files changed, 197 insertions(+), 241 deletions(-) rename {actix-http => awc}/src/client/config.rs (96%) rename {actix-http => awc}/src/client/connection.rs (89%) rename {actix-http => awc}/src/client/connector.rs (88%) rename {actix-http => awc}/src/client/error.rs (98%) rename {actix-http => awc}/src/client/h1proto.rs (92%) rename {actix-http => awc}/src/client/h2proto.rs (96%) rename {actix-http => awc}/src/client/mod.rs (95%) rename {actix-http => awc}/src/client/pool.rs (95%) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3273847c5..81595c92d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +* `client` module. [#2425] +* `trust-dns` feature. [#2425] + +[#2425]: https://github.com/actix/actix-web/pull/2425 ## 3.0.0-beta.11 - 2021-10-20 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a8fc4255f..3d45cc8ce 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -37,9 +37,6 @@ compress-brotli = ["brotli2", "__compress"] compress-gzip = ["flate2", "__compress"] compress-zstd = ["zstd", "__compress"] -# trust-dns as client dns resolver -trust-dns = ["trust-dns-resolver"] - # Internal (PRIVATE!) features used to aid testing and cheking feature status. # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] @@ -49,7 +46,7 @@ actix-service = "2.0.0" actix-codec = "0.4.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-tls = { version = "3.0.0-beta.7", features = ["accept", "connect"] } +actix-tls = { version = "3.0.0-beta.7", features = ["accept"] } ahash = "0.7" base64 = "0.13" @@ -82,8 +79,6 @@ brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.7", optional = true } -trust-dns-resolver = { version = "0.20.0", optional = true } - [dev-dependencies] actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 42ce4ffe4..bfb6b8c55 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -29,7 +29,6 @@ extern crate log; pub mod body; mod builder; -pub mod client; mod config; #[cfg(feature = "__compress")] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index ede72f219..002e7662e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -22,10 +22,10 @@ edition = "2018" default = [] # rustls -rustls = ["tls-rustls", "actix-http/rustls"] +rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"] # openssl -openssl = ["tls-openssl", "actix-http/openssl"] +openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.0" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 5a8235336..6eeb9ce51 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -30,10 +30,10 @@ features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-z default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] # openssl -openssl = ["tls-openssl", "actix-http/openssl"] +openssl = ["tls-openssl", "actix-tls/openssl"] # rustls -rustls = ["tls-rustls", "actix-http/rustls"] +rustls = ["tls-rustls", "actix-tls/rustls"] # Brotli algorithm content-encoding support compress-brotli = ["actix-http/compress-brotli", "__compress"] @@ -46,7 +46,7 @@ compress-zstd = ["actix-http/compress-zstd", "__compress"] cookies = ["cookie"] # trust-dns as dns resolver -trust-dns = ["actix-http/trust-dns"] +trust-dns = ["trust-dns-resolver"] # Internal (PRIVATE!) features used to aid testing and cheking feature status. # Don't rely on these whatsoever. They may disappear at anytime. @@ -57,13 +57,18 @@ actix-codec = "0.4.0" actix-service = "2.0.0" actix-http = "3.0.0-beta.11" actix-rt = { version = "2.1", default-features = false } +actix-tls = { version = "3.0.0-beta.7", features = ["connect"] } +actix-utils = "3.0.0" +ahash = "0.7" base64 = "0.13" bytes = "1" cfg-if = "1" -cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } +h2 = "0.3" +http = "0.2" itoa = "0.4" log =" 0.4" mime = "0.3" @@ -73,9 +78,15 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" +tokio = { version = "1", features = ["sync"] } + +cookie = { version = "0.15", features = ["percent-encode"], optional = true } + tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } +trust-dns-resolver = { version = "0.20.0", optional = true } + [dev-dependencies] actix-web = { version = "4.0.0-beta.10", features = ["openssl"] } actix-http = { version = "3.0.0-beta.11", features = ["openssl"] } diff --git a/awc/src/builder.rs b/awc/src/builder.rs index c594b4836..11ececa70 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -4,13 +4,11 @@ use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; -use actix_http::{ - client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, - http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, -}; +use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}; use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; +use crate::client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}; use crate::connect::DefaultConnector; use crate::error::SendRequestError; use crate::middleware::{NestTransform, Redirect, Transform}; diff --git a/actix-http/src/client/config.rs b/awc/src/client/config.rs similarity index 96% rename from actix-http/src/client/config.rs rename to awc/src/client/config.rs index 1c0405cbc..530c1e03b 100644 --- a/actix-http/src/client/config.rs +++ b/awc/src/client/config.rs @@ -1,5 +1,4 @@ -use std::net::IpAddr; -use std::time::Duration; +use std::{net::IpAddr, time::Duration}; const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB diff --git a/actix-http/src/client/connection.rs b/awc/src/client/connection.rs similarity index 89% rename from actix-http/src/client/connection.rs rename to awc/src/client/connection.rs index a30f651ca..97b96fc0a 100644 --- a/actix-http/src/client/connection.rs +++ b/awc/src/client/connection.rs @@ -12,10 +12,9 @@ use bytes::Bytes; use futures_core::future::LocalBoxFuture; use h2::client::SendRequest; -use crate::h1::ClientCodec; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::Payload; -use crate::{body::MessageBody, Error}; +use actix_http::{ + body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead, +}; use super::error::SendRequestError; use super::pool::Acquired; @@ -219,11 +218,7 @@ impl ConnectionType { } } - pub(super) fn from_h1( - io: Io, - created: time::Instant, - acquired: Acquired, - ) -> Self { + pub(super) fn from_h1(io: Io, created: time::Instant, acquired: Acquired) -> Self { Self::H1(H1Connection { io: Some(io), created, @@ -271,9 +266,7 @@ where Connection::Tls(ConnectionType::H2(conn)) => { h2proto::send_request(conn, head.into(), body).await } - _ => unreachable!( - "Plain Tcp connection can be used only in Http1 protocol" - ), + _ => unreachable!("Plain Tcp connection can be used only in Http1 protocol"), } }) } @@ -301,9 +294,7 @@ where Err(SendRequestError::TunnelNotSupported) } Connection::Tcp(ConnectionType::H2(_)) => { - unreachable!( - "Plain Tcp connection can be used only in Http1 protocol" - ) + unreachable!("Plain Tcp connection can be used only in Http1 protocol") } } }) @@ -321,12 +312,8 @@ where buf: &mut ReadBuf<'_>, ) -> Poll> { match self.get_mut() { - Connection::Tcp(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_read(cx, buf) - } - Connection::Tls(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_read(cx, buf) - } + Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_read(cx, buf), + Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_read(cx, buf), _ => unreachable!("H2Connection can not impl AsyncRead trait"), } } @@ -345,12 +332,8 @@ where buf: &[u8], ) -> Poll> { match self.get_mut() { - Connection::Tcp(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_write(cx, buf) - } - Connection::Tls(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_write(cx, buf) - } + Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf), + Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf), _ => unreachable!(H2_UNREACHABLE_WRITE), } } @@ -363,17 +346,10 @@ where } } - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.get_mut() { - Connection::Tcp(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_shutdown(cx) - } - Connection::Tls(ConnectionType::H1(conn)) => { - Pin::new(conn).poll_shutdown(cx) - } + Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx), + Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx), _ => unreachable!(H2_UNREACHABLE_WRITE), } } diff --git a/actix-http/src/client/connector.rs b/awc/src/client/connector.rs similarity index 88% rename from actix-http/src/client/connector.rs rename to awc/src/client/connector.rs index 4314511a3..8a162c4f8 100644 --- a/actix-http/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -8,6 +8,7 @@ use std::{ time::Duration, }; +use actix_http::Protocol; use actix_rt::{ net::{ActixStream, TcpStream}, time::{sleep, Sleep}, @@ -19,14 +20,13 @@ use actix_tls::connect::{ }; use futures_core::{future::LocalBoxFuture, ready}; use http::Uri; -use pin_project::pin_project; +use pin_project_lite::pin_project; use super::config::ConnectorConfig; use super::connection::{Connection, ConnectionIo}; use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; -use super::Protocol; enum SslConnector { #[allow(dead_code)] @@ -99,9 +99,7 @@ impl Connector<()> { /// Build TLS connector with openssl, based on supplied ALPN protocols #[cfg(all(feature = "openssl", not(feature = "rustls")))] fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_tls::connect::tls::openssl::{ - SslConnector as OpensslConnector, SslMethod, - }; + use actix_tls::connect::tls::openssl::{SslConnector as OpensslConnector, SslMethod}; use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); @@ -112,7 +110,7 @@ impl Connector<()> { let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); if let Err(err) = ssl.set_alpn_protos(&alpn) { - error!("Can not set ALPN protocol: {:?}", err); + log::error!("Can not set ALPN protocol: {:?}", err); } SslConnector::Openssl(ssl.build()) @@ -148,11 +146,8 @@ where // This remap is to hide ActixStream's trait methods. They are not meant to be called // from user code. Io: ActixStream + fmt::Debug + 'static, - S: Service< - TcpConnect, - Response = TcpConnection, - Error = TcpConnectError, - > + Clone + S: Service, Response = TcpConnection, Error = TcpConnectError> + + Clone + 'static, { /// Tcp connection timeout, i.e. max time to connect to remote host including dns name @@ -171,10 +166,7 @@ where #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. - pub fn ssl( - mut self, - connector: actix_tls::connect::ssl::openssl::SslConnector, - ) -> Self { + pub fn ssl(mut self, connector: actix_tls::connect::ssl::openssl::SslConnector) -> Self { self.ssl = SslConnector::Openssl(connector); self } @@ -328,10 +320,11 @@ where impl IntoConnectionIo for TcpConnection> { fn into_connection_io(self) -> (Box, Protocol) { let sock = self.into_parts().0; - let h2 = - sock.get_ref().1.alpn_protocol().map_or(false, |protos| { - protos.windows(2).any(|w| w == H2) - }); + let h2 = sock + .get_ref() + .1 + .alpn_protocol() + .map_or(false, |protos| protos.windows(2).any(|w| w == H2)); if h2 { (Box::new(sock), Protocol::Http2) } else { @@ -357,8 +350,8 @@ where let tcp_pool = ConnectionPool::new(tcp_service, tcp_config); let tls_config = self.config; - let tls_pool = tls_service - .map(move |tls_service| ConnectionPool::new(tls_service, tls_config)); + let tls_pool = + tls_service.map(move |tls_service| ConnectionPool::new(tls_service, tls_config)); ConnectorServicePriv { tcp_pool, tls_pool } } @@ -389,10 +382,12 @@ where } } -#[pin_project] -pub struct TcpConnectorFuture { - #[pin] - fut: Fut, +pin_project! { + #[project = TcpConnectorFutureProj] + pub struct TcpConnectorFuture { + #[pin] + fut: Fut, + } } impl Future for TcpConnectorFuture @@ -451,23 +446,25 @@ where } } -#[pin_project(project = TlsConnectorProj)] -#[allow(clippy::large_enum_variant)] -enum TlsConnectorFuture { - TcpConnect { - #[pin] - fut: Fut1, - tls_service: Option, - timeout: Duration, - }, - TlsConnect { - #[pin] - fut: Fut2, - #[pin] - timeout: Sleep, - }, -} +pin_project! { + #[project = TlsConnectorProj] + #[allow(clippy::large_enum_variant)] + enum TlsConnectorFuture { + TcpConnect { + #[pin] + fut: Fut1, + tls_service: Option, + timeout: Duration, + }, + TlsConnect { + #[pin] + fut: Fut2, + #[pin] + timeout: Sleep, + }, + } +} /// helper trait for generic over different TlsStream types between tls crates. trait IntoConnectionIo { fn into_connection_io(self) -> (Box, Protocol); @@ -475,12 +472,7 @@ trait IntoConnectionIo { impl Future for TlsConnectorFuture where - S: Service< - TcpConnection, - Response = Res, - Error = std::io::Error, - Future = Fut2, - >, + S: Service, Response = Res, Error = std::io::Error, Future = Fut2>, S::Response: IntoConnectionIo, Fut1: Future, ConnectError>>, Fut2: Future>, @@ -522,11 +514,7 @@ pub struct TcpConnectorInnerService { } impl TcpConnectorInnerService { - fn new( - service: S, - timeout: Duration, - local_address: Option, - ) -> Self { + fn new(service: S, timeout: Duration, local_address: Option) -> Self { Self { service, timeout, @@ -537,11 +525,8 @@ impl TcpConnectorInnerService { impl Service for TcpConnectorInnerService where - S: Service< - TcpConnect, - Response = TcpConnection, - Error = TcpConnectError, - > + Clone + S: Service, Response = TcpConnection, Error = TcpConnectError> + + Clone + 'static, { type Response = S::Response; @@ -564,12 +549,14 @@ where } } -#[pin_project] -pub struct TcpConnectorInnerFuture { - #[pin] - fut: Fut, - #[pin] - timeout: Sleep, +pin_project! { + #[project = TcpConnectorInnerFutureProj] + pub struct TcpConnectorInnerFuture { + #[pin] + fut: Fut, + #[pin] + timeout: Sleep, + } } impl Future for TcpConnectorInnerFuture @@ -618,12 +605,8 @@ where impl Service for ConnectorServicePriv where - S1: Service - + Clone - + 'static, - S2: Service - + Clone - + 'static, + S1: Service + Clone + 'static, + S2: Service + Clone + 'static, Io1: ConnectionIo, Io2: ConnectionIo, { @@ -643,38 +626,46 @@ where match req.uri.scheme_str() { Some("https") | Some("wss") => match self.tls_pool { None => ConnectorServiceFuture::SslIsNotSupported, - Some(ref pool) => ConnectorServiceFuture::Tls(pool.call(req)), + Some(ref pool) => ConnectorServiceFuture::Tls { + fut: pool.call(req), + }, + }, + _ => ConnectorServiceFuture::Tcp { + fut: self.tcp_pool.call(req), }, - _ => ConnectorServiceFuture::Tcp(self.tcp_pool.call(req)), } } } -#[pin_project(project = ConnectorServiceProj)] -pub enum ConnectorServiceFuture -where - S1: Service - + Clone - + 'static, - S2: Service - + Clone - + 'static, - Io1: ConnectionIo, - Io2: ConnectionIo, -{ - Tcp(#[pin] as Service>::Future), - Tls(#[pin] as Service>::Future), - SslIsNotSupported, +pin_project! { + #[project = ConnectorServiceFutureProj] + pub enum ConnectorServiceFuture + where + S1: Service, + S1: Clone, + S1: 'static, + S2: Service, + S2: Clone, + S2: 'static, + Io1: ConnectionIo, + Io2: ConnectionIo, + { + Tcp { + #[pin] + fut: as Service>::Future + }, + Tls { + #[pin] + fut: as Service>::Future + }, + SslIsNotSupported + } } impl Future for ConnectorServiceFuture where - S1: Service - + Clone - + 'static, - S2: Service - + Clone - + 'static, + S1: Service + Clone + 'static, + S2: Service + Clone + 'static, Io1: ConnectionIo, Io2: ConnectionIo, { @@ -682,9 +673,9 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { - ConnectorServiceProj::Tcp(fut) => fut.poll(cx).map_ok(Connection::Tcp), - ConnectorServiceProj::Tls(fut) => fut.poll(cx).map_ok(Connection::Tls), - ConnectorServiceProj::SslIsNotSupported => { + ConnectorServiceFutureProj::Tcp { fut } => fut.poll(cx).map_ok(Connection::Tcp), + ConnectorServiceFutureProj::Tls { fut } => fut.poll(cx).map_ok(Connection::Tls), + ConnectorServiceFutureProj::SslIsNotSupported => { Poll::Ready(Err(ConnectError::SslIsNotSupported)) } } diff --git a/actix-http/src/client/error.rs b/awc/src/client/error.rs similarity index 98% rename from actix-http/src/client/error.rs rename to awc/src/client/error.rs index 34833503b..0f3b1fdea 100644 --- a/actix-http/src/client/error.rs +++ b/awc/src/client/error.rs @@ -2,12 +2,13 @@ use std::{error::Error as StdError, fmt, io}; use derive_more::{Display, From}; +use actix_http::{ + error::{Error, ParseError}, + http::Error as HttpError, +}; #[cfg(feature = "openssl")] use actix_tls::accept::openssl::SslError; -use crate::error::{Error, ParseError}; -use crate::http::Error as HttpError; - /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] #[non_exhaustive] diff --git a/actix-http/src/client/h1proto.rs b/awc/src/client/h1proto.rs similarity index 92% rename from actix-http/src/client/h1proto.rs rename to awc/src/client/h1proto.rs index 65a30748c..3c2bb7cc1 100644 --- a/actix-http/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -5,24 +5,25 @@ use std::{ }; use actix_codec::Framed; +use actix_http::{ + body::{BodySize, MessageBody}, + error::PayloadError, + h1, + http::{ + header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, + StatusCode, + }, + Error, Payload, RequestHeadType, ResponseHead, +}; use actix_utils::future::poll_fn; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; use futures_util::SinkExt as _; - -use crate::h1; -use crate::http::{ - header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, - StatusCode, -}; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::Payload; -use crate::{error::PayloadError, Error}; +use pin_project_lite::pin_project; use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; -use crate::body::{BodySize, MessageBody}; pub(crate) async fn send_request( io: H1Connection, @@ -194,10 +195,11 @@ where Ok(()) } -#[pin_project::pin_project] -pub(crate) struct PlStream { - #[pin] - framed: Framed, h1::ClientPayloadCodec>, +pin_project! { + pub(crate) struct PlStream { + #[pin] + framed: Framed, h1::ClientPayloadCodec>, + } } impl PlStream { @@ -211,10 +213,7 @@ impl PlStream { impl Stream for PlStream { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); match ready!(this.framed.as_mut().next_item(cx)?) { diff --git a/actix-http/src/client/h2proto.rs b/awc/src/client/h2proto.rs similarity index 96% rename from actix-http/src/client/h2proto.rs rename to awc/src/client/h2proto.rs index b9d5f96bd..feb2dbd06 100644 --- a/actix-http/src/client/h2proto.rs +++ b/awc/src/client/h2proto.rs @@ -8,13 +8,12 @@ use h2::{ }; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, Method, Version}; +use log::trace; -use crate::{ +use actix_http::{ body::{BodySize, MessageBody}, header::HeaderMap, - message::{RequestHeadType, ResponseHead}, - payload::Payload, - Error, + Error, Payload, RequestHeadType, ResponseHead, }; use super::{ @@ -131,10 +130,7 @@ where Ok((head, payload)) } -async fn send_body( - body: B, - mut send: SendStream, -) -> Result<(), SendRequestError> +async fn send_body(body: B, mut send: SendStream) -> Result<(), SendRequestError> where B: MessageBody, B::Error: Into, @@ -184,8 +180,7 @@ where pub(crate) fn handshake( io: Io, config: &ConnectorConfig, -) -> impl Future, Connection), h2::Error>> -{ +) -> impl Future, Connection), h2::Error>> { let mut builder = Builder::new(); builder .initial_window_size(config.stream_window_size) diff --git a/actix-http/src/client/mod.rs b/awc/src/client/mod.rs similarity index 95% rename from actix-http/src/client/mod.rs rename to awc/src/client/mod.rs index 41d5fef2a..3abbf50a5 100644 --- a/actix-http/src/client/mod.rs +++ b/awc/src/client/mod.rs @@ -17,7 +17,6 @@ pub use actix_tls::connect::{ pub use self::connection::{Connection, ConnectionIo}; pub use self::connector::{Connector, ConnectorService}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; -pub use crate::Protocol; #[derive(Clone)] pub struct Connect { diff --git a/actix-http/src/client/pool.rs b/awc/src/client/pool.rs similarity index 95% rename from actix-http/src/client/pool.rs rename to awc/src/client/pool.rs index 88188038f..229c6324a 100644 --- a/actix-http/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -14,22 +14,20 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; +use actix_http::Protocol; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; use http::uri::Authority; -use pin_project::pin_project; +use pin_project_lite::pin_project; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; -use super::connection::{ - ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner, -}; +use super::connection::{ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; -use super::Protocol; #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub struct Key { @@ -152,9 +150,7 @@ where impl Service for ConnectionPool where - S: Service - + Clone - + 'static, + S: Service + Clone + 'static, Io: ConnectionIo, { type Response = ConnectionType; @@ -195,8 +191,8 @@ where let config = &inner.config; let idle_dur = now - c.used; let age = now - c.created; - let conn_ineligible = idle_dur > config.conn_keep_alive - || age > config.conn_lifetime; + let conn_ineligible = + idle_dur > config.conn_keep_alive || age > config.conn_lifetime; if conn_ineligible { // drop connections that are too old @@ -231,9 +227,7 @@ where // match the connection and spawn new one if did not get anything. match conn { - Some(conn) => { - Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)) - } + Some(conn) => Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)), None => { let (io, proto) = connector.call(req).await?; @@ -284,9 +278,7 @@ where let mut read_buf = ReadBuf::new(&mut buf); let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { - Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { - ConnectionState::Tainted - } + Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => ConnectionState::Tainted, Poll::Pending => ConnectionState::Live, _ => ConnectionState::Skip, @@ -302,11 +294,13 @@ struct PooledConnection { created: Instant, } -#[pin_project] -struct CloseConnection { - io: Io, - #[pin] - timeout: Sleep, +pin_project! { + #[project = CloseConnectionProj] + struct CloseConnection { + io: Io, + #[pin] + timeout: Sleep, + } } impl CloseConnection @@ -413,17 +407,11 @@ mod test { unimplemented!() } - fn poll_flush( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { unimplemented!() } - fn poll_shutdown( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 6a9fc4630..f27a8c368 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -8,16 +8,14 @@ use std::{ use actix_codec::Framed; use actix_http::{ - body::Body, - client::{ - Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, - }, - h1::ClientCodec, - Payload, RequestHead, RequestHeadType, ResponseHead, + body::Body, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, }; use actix_service::Service; use futures_core::{future::LocalBoxFuture, ready}; +use crate::client::{ + Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, +}; use crate::response::ClientResponse; pub type BoxConnectorService = Rc< diff --git a/awc/src/error.rs b/awc/src/error.rs index c83c5ebbf..d415efe95 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,15 +1,15 @@ //! HTTP client errors -pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; -pub use actix_http::error::PayloadError; -pub use actix_http::http::Error as HttpError; -pub use actix_http::ws::HandshakeError as WsHandshakeError; -pub use actix_http::ws::ProtocolError as WsProtocolError; +pub use actix_http::{ + error::PayloadError, + http::{header::HeaderValue, Error as HttpError, StatusCode}, + ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError}, +}; +use derive_more::{Display, From}; use serde_json::error::Error as JsonError; -use actix_http::http::{header::HeaderValue, StatusCode}; -use derive_more::{Display, From}; +pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; /// Websocket client error #[derive(Debug, Display, From)] diff --git a/awc/src/lib.rs b/awc/src/lib.rs index c0290ddcf..05f97aa3d 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -104,22 +104,8 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::{convert::TryFrom, rc::Rc, time::Duration}; - -#[cfg(feature = "cookies")] -pub use cookie; - -pub use actix_http::{client::Connector, http}; - -use actix_http::{ - client::{TcpConnect, TcpConnectError, TcpConnection}, - http::{Error as HttpError, HeaderMap, Method, Uri}, - RequestHead, -}; -use actix_rt::net::TcpStream; -use actix_service::Service; - mod builder; +mod client; mod connect; pub mod error; mod frozen; @@ -130,13 +116,29 @@ mod sender; pub mod test; pub mod ws; +pub use actix_http::http; +#[cfg(feature = "cookies")] +pub use cookie; + pub use self::builder::ClientBuilder; +pub use self::client::Connector; pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; +use std::{convert::TryFrom, rc::Rc, time::Duration}; + +use actix_http::{ + http::{Error as HttpError, HeaderMap, Method, Uri}, + RequestHead, +}; +use actix_rt::net::TcpStream; +use actix_service::Service; + +use self::client::{TcpConnect, TcpConnectError, TcpConnection}; + /// An asynchronous HTTP and WebSocket client. /// /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index a8c14d549..8a79a6596 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -9,7 +9,6 @@ use std::{ use actix_http::{ body::Body, - client::{InvalidUrl, SendRequestError}, http::{header, Method, StatusCode, Uri}, RequestHead, RequestHeadType, }; @@ -19,6 +18,7 @@ use futures_core::ready; use super::Transform; +use crate::client::{InvalidUrl, SendRequestError}; use crate::connect::{ConnectRequest, ConnectResponse}; use crate::ClientResponse; From 855e260fdb0f983537c463fd8d64c3eeb38ab546 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Tue, 26 Oct 2021 09:24:38 +0100 Subject: [PATCH 085/861] Add `html_utf8` content type. (#2423) --- CHANGES.md | 2 ++ src/http/header/content_type.rs | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9d7b3180d..3be41d468 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* `ContentType::html` now returns `Content-Type: text/html; charset=utf-8` instead of `Content-Type: text/html`. ## 4.0.0-beta.10 - 2021-10-20 diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index e1c419c22..230460003 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -60,52 +60,53 @@ crate::http::header::common_header! { } impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` + /// A constructor to easily create a `Content-Type: application/json` /// header. #[inline] pub fn json() -> ContentType { ContentType(mime::APPLICATION_JSON) } - /// A constructor to easily create a `Content-Type: text/plain; + /// A constructor to easily create a `Content-Type: text/plain; /// charset=utf-8` header. #[inline] pub fn plaintext() -> ContentType { ContentType(mime::TEXT_PLAIN_UTF_8) } - /// A constructor to easily create a `Content-Type: text/html` header. + /// A constructor to easily create a `Content-Type: text/html; charset=utf-8` + /// header. #[inline] pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) + ContentType(mime::TEXT_HTML_UTF_8) } - /// A constructor to easily create a `Content-Type: text/xml` header. + /// A constructor to easily create a `Content-Type: text/xml` header. #[inline] pub fn xml() -> ContentType { ContentType(mime::TEXT_XML) } - /// A constructor to easily create a `Content-Type: + /// A constructor to easily create a `Content-Type: /// application/www-form-url-encoded` header. #[inline] pub fn form_url_encoded() -> ContentType { ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) } - /// A constructor to easily create a `Content-Type: image/jpeg` header. + /// A constructor to easily create a `Content-Type: image/jpeg` header. #[inline] pub fn jpeg() -> ContentType { ContentType(mime::IMAGE_JPEG) } - /// A constructor to easily create a `Content-Type: image/png` header. + /// A constructor to easily create a `Content-Type: image/png` header. #[inline] pub fn png() -> ContentType { ContentType(mime::IMAGE_PNG) } - /// A constructor to easily create a `Content-Type: + /// A constructor to easily create a `Content-Type: /// application/octet-stream` header. #[inline] pub fn octet_stream() -> ContentType { From be9530eb729035cf4225753a2d6f64a12e0e6140 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 26 Oct 2021 20:16:48 +0800 Subject: [PATCH 086/861] avoid building actix-tls with no-default-features (#2426) --- actix-http/Cargo.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3d45cc8ce..7d39e000a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -27,10 +27,10 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["actix-tls/openssl"] +openssl = ["actix-tls/accept", "actix-tls/openssl"] # rustls support -rustls = ["actix-tls/rustls"] +rustls = ["actix-tls/accept", "actix-tls/rustls"] # enable compression support compress-brotli = ["brotli2", "__compress"] @@ -46,7 +46,6 @@ actix-service = "2.0.0" actix-codec = "0.4.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-tls = { version = "3.0.0-beta.7", features = ["accept"] } ahash = "0.7" base64 = "0.13" @@ -74,6 +73,9 @@ sha-1 = "0.9" smallvec = "1.6.1" tokio = { version = "1.2", features = ["sync"] } +# tls +actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } + # compression brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } From ec6d284a8e393d8c8f5bf60eece49abbab62658f Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sun, 31 Oct 2021 16:19:21 +0300 Subject: [PATCH 087/861] improve "data no configured" message (#2429) --- src/data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data.rs b/src/data.rs index 7e01d3462..d27ad196b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -137,7 +137,7 @@ impl FromRequest for Data { type_name::(), ); err(ErrorInternalServerError( - "App data is not configured, to configure use App::data()", + "App data is not configured, to configure construct it with web::Data::new() and pass it to App::app_data()", )) } } From 6ec2d7b90966bc55b72ee4c90129074742d087bc Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Nov 2021 23:15:23 +0800 Subject: [PATCH 088/861] add keep alive to h2 through ping pong (#2433) --- actix-http/src/h2/dispatcher.rs | 147 ++++++++++++++++++-------- actix-http/tests/test_h2_ping_pong.rs | 77 ++++++++++++++ 2 files changed, 180 insertions(+), 44 deletions(-) create mode 100644 actix-http/tests/test_h2_ping_pong.rs diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index ea149b1e0..7326dfff1 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -10,11 +10,15 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::time::Sleep; use actix_service::Service; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; use futures_core::ready; -use h2::server::{Connection, SendResponse}; +use h2::{ + server::{Connection, SendResponse}, + Ping, PingPong, +}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; use pin_project_lite::pin_project; @@ -36,29 +40,46 @@ pin_project! { on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, - _phantom: PhantomData, + ping_pong: Option, + _phantom: PhantomData } } -impl Dispatcher { +impl Dispatcher +where + T: AsyncRead + AsyncWrite + Unpin, +{ pub(crate) fn new( flow: Rc>, - connection: Connection, + mut connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, ) -> Self { + let ping_pong = config.keep_alive_timer().map(|timer| H2PingPong { + timer: Box::pin(timer), + on_flight: false, + ping_pong: connection.ping_pong().unwrap(), + }); + Self { flow, config, peer_addr, connection, on_connect_data, + ping_pong, _phantom: PhantomData, } } } +struct H2PingPong { + timer: Pin>, + on_flight: bool, + ping_pong: PingPong, +} + impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, @@ -77,54 +98,92 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - while let Some((req, tx)) = - ready!(Pin::new(&mut this.connection).poll_accept(cx)?) - { - let (parts, body) = req.into_parts(); - let pl = crate::h2::Payload::new(body); - let pl = Payload::::H2(pl); - let mut req = Request::with_payload(pl); + loop { + match Pin::new(&mut this.connection).poll_accept(cx)? { + Poll::Ready(Some((req, tx))) => { + let (parts, body) = req.into_parts(); + let pl = crate::h2::Payload::new(body); + let pl = Payload::::H2(pl); + let mut req = Request::with_payload(pl); - let head = req.head_mut(); - head.uri = parts.uri; - head.method = parts.method; - head.version = parts.version; - head.headers = parts.headers.into(); - head.peer_addr = this.peer_addr; + let head = req.head_mut(); + head.uri = parts.uri; + head.method = parts.method; + head.version = parts.version; + head.headers = parts.headers.into(); + head.peer_addr = this.peer_addr; - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + // merge on_connect_ext data into request extensions + this.on_connect_data.merge_into(&mut req); - let fut = this.flow.service.call(req); - let config = this.config.clone(); + let fut = this.flow.service.call(req); + let config = this.config.clone(); - // multiplex request handling with spawn task - actix_rt::spawn(async move { - // resolve service call and send response. - let res = match fut.await { - Ok(res) => handle_response(res.into(), tx, config).await, - Err(err) => { - let res: Response = err.into(); - handle_response(res, tx, config).await - } - }; + // multiplex request handling with spawn task + actix_rt::spawn(async move { + // resolve service call and send response. + let res = match fut.await { + Ok(res) => handle_response(res.into(), tx, config).await, + Err(err) => { + let res: Response = err.into(); + handle_response(res, tx, config).await + } + }; - // log error. - if let Err(err) = res { - match err { - DispatchError::SendResponse(err) => { - trace!("Error sending HTTP/2 response: {:?}", err) + // log error. + if let Err(err) = res { + match err { + DispatchError::SendResponse(err) => { + trace!("Error sending HTTP/2 response: {:?}", err) + } + DispatchError::SendData(err) => warn!("{:?}", err), + DispatchError::ResponseBody(err) => { + error!("Response payload stream error: {:?}", err) + } + } } - DispatchError::SendData(err) => warn!("{:?}", err), - DispatchError::ResponseBody(err) => { - error!("Response payload stream error: {:?}", err) - } - } + }); } - }); - } + Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => match this.ping_pong.as_mut() { + Some(ping_pong) => loop { + if ping_pong.on_flight { + // When have on flight ping pong. poll pong and and keep alive timer. + // on success pong received update keep alive timer to determine the next timing of + // ping pong. + match ping_pong.ping_pong.poll_pong(cx)? { + Poll::Ready(_) => { + ping_pong.on_flight = false; - Poll::Ready(Ok(())) + let dead_line = + this.config.keep_alive_expire().unwrap(); + ping_pong.timer.as_mut().reset(dead_line); + } + Poll::Pending => { + return ping_pong + .timer + .as_mut() + .poll(cx) + .map(|_| Ok(())) + } + } + } else { + // When there is no on flight ping pong. keep alive timer is used to wait for next + // timing of ping pong. Therefore at this point it serves as an interval instead. + ready!(ping_pong.timer.as_mut().poll(cx)); + + ping_pong.ping_pong.send_ping(Ping::opaque())?; + + let dead_line = this.config.keep_alive_expire().unwrap(); + ping_pong.timer.as_mut().reset(dead_line); + + ping_pong.on_flight = true; + } + }, + None => return Poll::Pending, + }, + } + } } } diff --git a/actix-http/tests/test_h2_ping_pong.rs b/actix-http/tests/test_h2_ping_pong.rs new file mode 100644 index 000000000..5e03785a1 --- /dev/null +++ b/actix-http/tests/test_h2_ping_pong.rs @@ -0,0 +1,77 @@ +use std::io; + +use actix_http::{error::Error, HttpService, Response}; +use actix_server::Server; + +#[actix_rt::test] +async fn h2_ping_pong() -> io::Result<()> { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + + let lst = std::net::TcpListener::bind("127.0.0.1:0")?; + + let addr = lst.local_addr().unwrap(); + + let join = std::thread::spawn(move || { + actix_rt::System::new().block_on(async move { + let handle = Server::build() + .disable_signals() + .workers(1) + .listen("h2_ping_pong", lst, || { + HttpService::build() + .keep_alive(3) + .h2(|_| async { Ok::<_, Error>(Response::ok()) }) + .tcp() + })? + .run(); + + tx.send(handle.clone()).unwrap(); + + handle.await + }) + }); + + let handle = rx.recv().unwrap(); + + let (sync_tx, rx) = std::sync::mpsc::sync_channel(1); + + // use a separate thread for h2 client so it can be blocked. + std::thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + let stream = tokio::net::TcpStream::connect(addr).await.unwrap(); + + let (mut tx, conn) = h2::client::handshake(stream).await.unwrap(); + + tokio::spawn(async move { conn.await.unwrap() }); + + let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap(); + let res = res.await.unwrap(); + + assert_eq!(res.status().as_u16(), 200); + + sync_tx.send(()).unwrap(); + + // intentionally block the client thread so it can not answer ping pong. + std::thread::sleep(std::time::Duration::from_secs(1000)); + }) + }); + + rx.recv().unwrap(); + + let now = std::time::Instant::now(); + + // stop server gracefully. this step would take up to 30 seconds. + handle.stop(true).await; + + // join server thread. only when connection are all gone this step would finish. + join.join().unwrap()?; + + // check the time used for join server thread so it's known that the server shutdown + // is from keep alive and not server graceful shutdown timeout. + assert!(now.elapsed() < std::time::Duration::from_secs(30)); + + Ok(()) +} From 5e554dca35844c241e813663da09e20575d586d3 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 4 Nov 2021 23:57:55 +0800 Subject: [PATCH 089/861] fix awc clippy warning (#2431) --- awc/src/client/pool.rs | 3 ++- awc/tests/test_client.rs | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs index 229c6324a..9d130412b 100644 --- a/awc/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -19,6 +19,7 @@ use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; +use futures_util::FutureExt; use http::uri::Authority; use pin_project_lite::pin_project; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; @@ -201,7 +202,7 @@ where // check if the connection is still usable if let ConnectionInnerType::H1(ref mut io) = c.conn { let check = ConnectionCheckFuture { io }; - match check.await { + match check.now_or_never().expect("ConnectionCheckFuture must never yield with Poll::Pending.") { ConnectionState::Tainted => { inner.close(c.conn); continue; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 615789fb3..a0af0cab6 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -795,17 +795,15 @@ async fn client_unread_response() { let lst = std::net::TcpListener::bind(addr).unwrap(); 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\ + let (mut stream, _) = lst.accept().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!", - ); - } + ); }); // client request From c020cedb631d08528d42de24741975b0a5b6e0b6 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sun, 7 Nov 2021 12:02:23 -0500 Subject: [PATCH 090/861] Log internal server errors (#2387) --- src/middleware/logger.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 961eca496..f3f6aa5a9 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -275,9 +275,7 @@ where }; if let Some(error) = res.response().error() { - if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR { - debug!("Error in response: {:?}", error); - } + debug!("Error in response: {:?}", error); } if let Some(ref mut format) = this.format { From 2754608f3c98821a28c4f2a67d2ef40612587c04 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 8 Nov 2021 02:46:43 +0000 Subject: [PATCH 091/861] fix codegen tests --- src/middleware/logger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f3f6aa5a9..b4d100b3e 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -22,7 +22,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ dev::{BodySize, MessageBody}, - http::{HeaderName, StatusCode}, + http::HeaderName, service::{ServiceRequest, ServiceResponse}, Error, HttpResponse, Result, }; From a2f59c02f72c0c50cb69dd9e17439f70b6a8b6db Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Nov 2021 04:03:33 +0000 Subject: [PATCH 092/861] bump actix-server to beta 9 (#2442) --- CHANGES.md | 12 ++- Cargo.toml | 10 +-- README.md | 4 +- actix-files/Cargo.toml | 8 +- actix-http-test/CHANGES.md | 7 ++ actix-http-test/Cargo.toml | 13 +-- actix-http-test/README.md | 4 +- actix-http-test/src/lib.rs | 105 ++++++++++++++-------- actix-http/CHANGES.md | 7 ++ actix-http/Cargo.toml | 10 +-- actix-http/README.md | 4 +- actix-http/tests/test_h2_ping_pong.rs | 6 +- actix-http/tests/test_openssl.rs | 33 +++---- actix-http/tests/test_rustls.rs | 5 +- actix-http/tests/test_server.rs | 82 +++++++++++++---- actix-multipart/Cargo.toml | 4 +- actix-router/Cargo.toml | 2 +- actix-test/CHANGES.md | 4 + actix-test/Cargo.toml | 11 +-- actix-test/src/lib.rs | 79 +++++++++++----- actix-web-actors/Cargo.toml | 8 +- actix-web-codegen/Cargo.toml | 4 +- awc/CHANGES.md | 3 + awc/Cargo.toml | 16 ++-- awc/README.md | 4 +- awc/src/client/connection.rs | 3 +- awc/tests/test_rustls_client.rs | 3 +- examples/on_connect.rs | 1 + src/dev.rs | 2 +- src/server.rs | 4 +- tests/test_httpserver.rs | 125 ++++++++++++-------------- tests/test_server.rs | 68 +++++++++++++- 32 files changed, 415 insertions(+), 236 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3be41d468..4ecbd0c2e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,18 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.11 - 2021-11-15 +### Added +* Re-export `dev::ServerHandle` from `actix-server`. [#2442] + ### Changed -* `ContentType::html` now returns `Content-Type: text/html; charset=utf-8` instead of `Content-Type: text/html`. +* `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] + +[#2423]: https://github.com/actix/actix-web/pull/2423 +[#2442]: https://github.com/actix/actix-web/pull/2442 ## 4.0.0-beta.10 - 2021-10-20 diff --git a/Cargo.toml b/Cargo.toml index 152282207..8ca34c924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.10" +version = "4.0.0-beta.11" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -69,12 +69,12 @@ __compress = [] actix-codec = "0.4.0" actix-macros = "0.2.3" actix-rt = "2.2" -actix-server = "2.0.0-beta.3" +actix-server = "2.0.0-beta.9" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } -actix-http = "3.0.0-beta.11" +actix-http = "3.0.0-beta.12" actix-router = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.5" @@ -104,8 +104,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.9", features = ["openssl"] } +actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } +awc = { version = "3.0.0-beta.10", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/README.md b/README.md index 25b595361..9444f130d 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.10)](https://docs.rs/actix-web/4.0.0-beta.10) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.11)](https://docs.rs/actix-web/4.0.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.10) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.11)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3d7340607..bbb9f551a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -15,8 +15,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.10", default-features = false } -actix-http = "3.0.0-beta.11" +actix-web = { version = "4.0.0-beta.11", default-features = false } +actix-http = "3.0.0-beta.12" actix-service = "2.0.0" actix-utils = "3.0.0" @@ -33,5 +33,5 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.10" -actix-test = "0.1.0-beta.5" +actix-web = "4.0.0-beta.11" +actix-test = "0.1.0-beta.6" diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 98b197bcf..ea00acb0c 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,8 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.6 - 2021-11-15 +* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] * Minimum supported Rust version (MSRV) is now 1.52. +[#2442]: https://github.com/actix/actix-web/pull/2442 + ## 3.0.0-beta.5 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index d3fc8a47f..b111f8685 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.5" +version = "3.0.0-beta.6" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] @@ -34,13 +34,13 @@ actix-codec = "0.4.0" actix-tls = "3.0.0-beta.7" actix-utils = "3.0.0" actix-rt = "2.2" -actix-server = "2.0.0-beta.3" -awc = { version = "3.0.0-beta.9", default-features = false } +actix-server = "2.0.0-beta.9" +awc = { version = "3.0.0-beta.10", default-features = false } base64 = "0.13" bytes = "1" futures-core = { version = "0.3.7", default-features = false } -http = "0.2.2" +http = "0.2.5" log = "0.4" socket2 = "0.4" serde = "1.0" @@ -48,7 +48,8 @@ serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } +tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.10", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.11" +actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.12" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 6bf0d710a..3eee66451 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http-test/3.0.0-beta.5) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http-test/3.0.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.5) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index c7b083b5e..cda98cea5 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -7,7 +7,7 @@ #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; -use std::{net, sync::mpsc, thread, time::Duration}; +use std::{net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; @@ -19,6 +19,7 @@ use bytes::Bytes; use futures_core::stream::Stream; use http::Method; use socket2::{Domain, Protocol, Socket, Type}; +use tokio::sync::mpsc; /// Start test server /// @@ -55,12 +56,13 @@ pub async fn test_server>(factory: F) -> TestServer test_server_with_addr(tcp, factory).await } -/// Start [`test server`](test_server()) on a concrete Address +/// Start [`test server`](test_server()) on an existing address binding. pub async fn test_server_with_addr>( tcp: net::TcpListener, factory: F, ) -> TestServer { - let (tx, rx) = mpsc::channel(); + let (started_tx, started_rx) = std::sync::mpsc::channel(); + let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1); // run server in separate thread thread::spawn(move || { @@ -68,59 +70,73 @@ pub async fn test_server_with_addr>( let local_addr = tcp.local_addr().unwrap(); let srv = Server::build() - .listen("test", tcp, factory)? .workers(1) - .disable_signals(); + .disable_signals() + .listen("test", tcp, factory) + .expect("test server could not be created"); - sys.block_on(async { - srv.run(); - tx.send((System::current(), local_addr)).unwrap(); - }); + let srv = srv.run(); + started_tx + .send((System::current(), srv.handle(), local_addr)) + .unwrap(); - sys.run() + // drive server loop + sys.block_on(srv).unwrap(); + + // start system event loop + sys.run().unwrap(); + + // notify TestServer that server and system have shut down + // all thread managed resources should be dropped at this point + let _ = thread_stop_tx.send(()); }); - let (system, addr) = rx.recv().unwrap(); + let (system, server, addr) = started_rx.recv().unwrap(); let client = { + #[cfg(feature = "openssl")] let connector = { - #[cfg(feature = "openssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .conn_lifetime(Duration::from_secs(0)) - .timeout(Duration::from_millis(30000)) - .ssl(builder.build()) - } - #[cfg(not(feature = "openssl"))] - { - Connector::new() - .conn_lifetime(Duration::from_secs(0)) - .timeout(Duration::from_millis(30000)) - } + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + Connector::new() + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) + .ssl(builder.build()) + }; + + #[cfg(not(feature = "openssl"))] + let connector = { + Connector::new() + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) }; Client::builder().connector(connector).finish() }; TestServer { - addr, + server, client, system, + addr, + thread_stop_rx, } } /// Test server controller pub struct TestServer { + server: actix_server::ServerHandle, + client: awc::Client, + system: actix_rt::System, addr: net::SocketAddr, - client: Client, - system: System, + thread_stop_rx: mpsc::Receiver<()>, } impl TestServer { @@ -257,15 +273,32 @@ impl TestServer { self.client.headers() } - /// Stop HTTP server - fn stop(&mut self) { + /// Gracefully stop HTTP server. + /// + /// Waits for spawned `Server` and `System` to shutdown gracefully. + pub async fn stop(&mut self) { + // signal server to stop + self.server.stop(true).await; + + // also signal system to stop + // though this is handled by `ServerBuilder::exit_system` too self.system.stop(); + + // wait for thread to be stopped but don't care about result + let _ = self.thread_stop_rx.recv().await; } } impl Drop for TestServer { fn drop(&mut self) { - self.stop() + // calls in this Drop impl should be enough to shut down the server, system, and thread + // without needing to await anything + + // signal server to stop + let _ = self.server.stop(true); + + // signal system to stop + self.system.stop(); } } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 81595c92d..9ec75b4bc 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,18 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.12 - 2021-11-15 +### Changed +* Update `actix-server` to `2.0.0-beta.9`. [#2442] + ### Removed * `client` module. [#2425] * `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 +[#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.11 - 2021-10-20 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7d39e000a..784312445 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.11" +version = "3.0.0-beta.12" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] @@ -57,7 +57,7 @@ encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.1" -http = "0.2.2" +http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" itoa = "0.4" @@ -71,7 +71,6 @@ pin-project-lite = "0.2" rand = "0.8" sha-1 = "0.9" smallvec = "1.6.1" -tokio = { version = "1.2", features = ["sync"] } # tls actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } @@ -82,8 +81,8 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.7", optional = true } [dev-dependencies] -actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-server = "2.0.0-beta.9" +actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } @@ -95,6 +94,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } +tokio = { version = "1.2", features = ["net", "rt"] } [[example]] name = "ws" diff --git a/actix-http/README.md b/actix-http/README.md index 5b1e552fd..536d17074 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.11)](https://docs.rs/actix-http/3.0.0-beta.11) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http/3.0.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.11) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.12) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/tests/test_h2_ping_pong.rs b/actix-http/tests/test_h2_ping_pong.rs index 5e03785a1..30ce9aa51 100644 --- a/actix-http/tests/test_h2_ping_pong.rs +++ b/actix-http/tests/test_h2_ping_pong.rs @@ -13,7 +13,7 @@ async fn h2_ping_pong() -> io::Result<()> { let join = std::thread::spawn(move || { actix_rt::System::new().block_on(async move { - let handle = Server::build() + let srv = Server::build() .disable_signals() .workers(1) .listen("h2_ping_pong", lst, || { @@ -24,9 +24,9 @@ async fn h2_ping_pong() -> io::Result<()> { })? .run(); - tx.send(handle.clone()).unwrap(); + tx.send(srv.handle()).unwrap(); - handle.await + srv.await }) }); diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index a58d0cc70..0eaaabcc7 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -8,7 +8,7 @@ use actix_http::{ body::{AnyBody, Body, SizedStream}, error::PayloadError, http::{ - header::{self, HeaderName, HeaderValue}, + header::{self, HeaderValue}, Method, StatusCode, Version, }, Error, HttpMessage, HttpService, Request, Response, @@ -143,38 +143,25 @@ async fn test_h2_content_length() { }) .await; - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); + static VALUE: HeaderValue = HeaderValue::from_static("0"); { - for &i in &[0] { - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let _response = req.await.expect_err("should timeout on recv 1xx frame"); - // assert_eq!(response.headers().get(&header), None); + let req = srv.request(Method::HEAD, srv.surl("/0")).send(); + req.await.expect_err("should timeout on recv 1xx frame"); - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let _response = req.await.expect_err("should timeout on recv 1xx frame"); - // assert_eq!(response.headers().get(&header), None); - } + let req = srv.request(Method::GET, srv.surl("/0")).send(); + req.await.expect_err("should timeout on recv 1xx frame"); - for &i in &[1] { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } + let req = srv.request(Method::GET, srv.surl("/1")).send(); + let response = req.await.unwrap(); + assert!(response.headers().get("content-length").is_none()); for &i in &[2, 3] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) .send(); let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); + assert_eq!(response.headers().get("content-length"), Some(&VALUE)); } } } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 924ef49ad..a9f6e99f8 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -26,10 +26,7 @@ use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; use futures_core::Stream; use futures_util::stream::{once, StreamExt as _}; -use rustls::{ - Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore, - ServerConfig as RustlsServerConfig, ServerName, -}; +use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName}; use rustls_pemfile::{certs, pkcs8_private_keys}; async fn load_body(mut stream: S) -> Result diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index c04aeae00..ea78ce113 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -24,7 +24,7 @@ use regex::Regex; #[actix_rt::test] async fn test_h1() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -39,11 +39,13 @@ async fn test_h1() { let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); + + srv.stop().await; } #[actix_rt::test] async fn test_h1_2() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -59,6 +61,8 @@ async fn test_h1_2() { let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); + + srv.stop().await; } #[derive(Debug, Display, Error)] @@ -73,7 +77,7 @@ impl From for Response { #[actix_rt::test] async fn test_expect_continue() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { if req.head().uri.query() == Some("yes=") { @@ -98,11 +102,13 @@ async fn test_expect_continue() { let mut data = String::new(); let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); + + srv.stop().await; } #[actix_rt::test] async fn test_expect_continue_h1() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { sleep(Duration::from_millis(20)).then(move |_| { @@ -129,6 +135,8 @@ async fn test_expect_continue_h1() { let mut data = String::new(); let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); + + srv.stop().await; } #[actix_rt::test] @@ -136,7 +144,7 @@ async fn test_chunked_payload() { let chunk_sizes = vec![32768, 32, 32768]; let total_size: usize = chunk_sizes.iter().sum(); - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(fn_service(|mut request: Request| { request @@ -188,11 +196,13 @@ async fn test_chunked_payload() { }; assert_eq!(returned_size, total_size); + + srv.stop().await; } #[actix_rt::test] async fn test_slow_request() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .client_timeout(100) .finish(|_| ok::<_, Infallible>(Response::ok())) @@ -205,11 +215,13 @@ async fn test_slow_request() { let mut data = String::new(); let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + + srv.stop().await; } #[actix_rt::test] async fn test_http1_malformed_request() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() @@ -221,11 +233,13 @@ async fn test_http1_malformed_request() { let mut data = String::new(); let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 400 Bad Request")); + + srv.stop().await; } #[actix_rt::test] async fn test_http1_keepalive() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() @@ -242,11 +256,13 @@ async fn test_http1_keepalive() { let mut data = vec![0; 1024]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + srv.stop().await; } #[actix_rt::test] async fn test_http1_keepalive_timeout() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .keep_alive(1) .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -264,11 +280,13 @@ async fn test_http1_keepalive_timeout() { let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); + + srv.stop().await; } #[actix_rt::test] async fn test_http1_keepalive_close() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() @@ -285,11 +303,13 @@ async fn test_http1_keepalive_close() { let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); + + srv.stop().await; } #[actix_rt::test] async fn test_http10_keepalive_default_close() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() @@ -305,11 +325,13 @@ async fn test_http10_keepalive_default_close() { let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); + + srv.stop().await; } #[actix_rt::test] async fn test_http10_keepalive() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() @@ -332,11 +354,13 @@ async fn test_http10_keepalive() { let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); + + srv.stop().await; } #[actix_rt::test] async fn test_http1_keepalive_disabled() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -353,6 +377,8 @@ async fn test_http1_keepalive_disabled() { let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); + + srv.stop().await; } #[actix_rt::test] @@ -362,7 +388,7 @@ async fn test_content_length() { StatusCode, }; - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); @@ -400,6 +426,8 @@ async fn test_content_length() { assert_eq!(response.headers().get(&header), Some(&value)); } } + + srv.stop().await; } #[actix_rt::test] @@ -439,6 +467,8 @@ async fn test_h1_headers() { // read response let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from(data2)); + + srv.stop().await; } const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -478,6 +508,8 @@ async fn test_h1_body() { // read response let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -503,6 +535,8 @@ async fn test_h1_head_empty() { // read response let bytes = srv.load_body(response).await.unwrap(); assert!(bytes.is_empty()); + + srv.stop().await; } #[actix_rt::test] @@ -528,11 +562,13 @@ async fn test_h1_head_binary() { // read response let bytes = srv.load_body(response).await.unwrap(); assert!(bytes.is_empty()); + + srv.stop().await; } #[actix_rt::test] async fn test_h1_head_binary2() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .tcp() @@ -549,6 +585,8 @@ async fn test_h1_head_binary2() { .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } + + srv.stop().await; } #[actix_rt::test] @@ -571,6 +609,8 @@ async fn test_h1_body_length() { // read response let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -606,6 +646,8 @@ async fn test_h1_body_chunked_explicit() { // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -635,6 +677,8 @@ async fn test_h1_body_chunked_implicit() { // read response let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -662,6 +706,8 @@ async fn test_h1_response_http_error_handling() { bytes, Bytes::from_static(b"error processing HTTP: failed to parse header value") ); + + srv.stop().await; } #[derive(Debug, Display, Error)] @@ -689,11 +735,13 @@ async fn test_h1_service_error() { // read response let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); + + srv.stop().await; } #[actix_rt::test] async fn test_h1_on_connect() { - let srv = test_server(|| { + let mut srv = test_server(|| { HttpService::build() .on_connect_ext(|_, data| { data.insert(20isize); @@ -708,4 +756,6 @@ async fn test_h1_on_connect() { let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); + + srv.stop().await; } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 92637cef9..b2f3e391c 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -14,7 +14,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.10", default-features = false } +actix-web = { version = "4.0.0-beta.11", default-features = false } actix-utils = "3.0.0" bytes = "1" @@ -29,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.11" +actix-http = "3.0.0-beta.12" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index e32f0edd6..b95bca505 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -30,7 +30,7 @@ serde = "1" [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } firestorm = { version = "0.4", features = ["enable_system_time"] } -http = "0.2.3" +http = "0.2.5" serde = { version = "1", features = ["derive"] } [[bench]] diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 070892581..5c22139ae 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.6 - 2021-11-15 +* No significant changes from `0.1.0-beta.5`. + + ## 0.1.0-beta.5 - 2021-10-20 * Updated rustls to v0.20. [#2414] * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 002e7662e..58c0d31a5 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.5" +version = "0.1.0-beta.6" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.0" -actix-http = "3.0.0-beta.11" -actix-http-test = "3.0.0-beta.5" +actix-http = "3.0.0-beta.12" +actix-http-test = "3.0.0-beta.6" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.10", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.9", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.10", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } @@ -45,3 +45,4 @@ serde_json = "1" serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true } +tokio = { version = "1.2", features = ["sync"] } diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 23a7eeba1..cf5738aa0 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -31,7 +31,7 @@ extern crate tls_openssl as openssl; #[cfg(feature = "rustls")] extern crate tls_rustls as rustls; -use std::{error::Error as StdError, fmt, net, sync::mpsc, thread, time}; +use std::{error::Error as StdError, fmt, net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; @@ -41,8 +41,9 @@ use actix_http::{ }; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_web::{ - dev::{AppConfig, MessageBody, Server, Service}, - rt, web, Error, + dev::{AppConfig, MessageBody, Server, ServerHandle, Service}, + rt::{self, System}, + web, Error, }; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use futures_core::Stream; @@ -52,6 +53,7 @@ pub use actix_web::test::{ call_service, default_service, init_service, load_stream, ok_service, read_body, read_body_json, read_response, read_response_json, TestRequest, }; +use tokio::sync::mpsc; /// Start default [`TestServer`]. /// @@ -128,7 +130,11 @@ where B: MessageBody + 'static, B::Error: Into>, { - let (tx, rx) = mpsc::channel(); + // for sending handles and server info back from the spawned thread + let (started_tx, started_rx) = std::sync::mpsc::channel(); + + // for signaling the shutdown of spawned server and system + let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1); let tls = match cfg.stream { StreamType::Tcp => false, @@ -138,7 +144,7 @@ where StreamType::Rustls(_) => true, }; - // run server in separate thread + // run server in separate orphaned thread thread::spawn(move || { let sys = rt::System::new(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); @@ -146,7 +152,7 @@ where let factory = factory.clone(); let srv_cfg = cfg.clone(); let timeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals(); + let builder = Server::build().workers(1).disable_signals().system_exit(); let srv = match srv_cfg.stream { StreamType::Tcp => match srv_cfg.tp { @@ -275,17 +281,25 @@ where }), }, } - .unwrap(); + .expect("test server could not be created"); - sys.block_on(async { - let srv = srv.run(); - tx.send((rt::System::current(), srv, local_addr)).unwrap(); - }); + let srv = srv.run(); + started_tx + .send((System::current(), srv.handle(), local_addr)) + .unwrap(); - sys.run() + // drive server loop + sys.block_on(srv).unwrap(); + + // start system event loop + sys.run().unwrap(); + + // notify TestServer that server and system have shut down + // all thread managed resources should be dropped at this point + let _ = thread_stop_tx.send(()); }); - let (system, server, addr) = rx.recv().unwrap(); + let (system, server, addr) = started_rx.recv().unwrap(); let client = { let connector = { @@ -299,15 +313,15 @@ where .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) .ssl(builder.build()) } #[cfg(not(feature = "openssl"))] { Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) } }; @@ -315,11 +329,12 @@ where }; TestServer { - addr, + server, + thread_stop_rx, client, system, + addr, tls, - server, } } @@ -405,11 +420,12 @@ impl TestServerConfig { /// /// See [`start`] for usage example. pub struct TestServer { - addr: net::SocketAddr, + server: ServerHandle, + thread_stop_rx: mpsc::Receiver<()>, client: awc::Client, system: rt::System, + addr: net::SocketAddr, tls: bool, - server: Server, } impl TestServer { @@ -505,15 +521,30 @@ impl TestServer { } /// Gracefully stop HTTP server. - pub async fn stop(self) { + /// + /// Waits for spawned `Server` and `System` to shutdown gracefully. + pub async fn stop(mut self) { + // signal server to stop self.server.stop(true).await; + + // also signal system to stop + // though this is handled by `ServerBuilder::exit_system` too self.system.stop(); - rt::time::sleep(time::Duration::from_millis(100)).await; + + // wait for thread to be stopped but don't care about result + let _ = self.thread_stop_rx.recv().await; } } impl Drop for TestServer { fn drop(&mut self) { - self.system.stop() + // calls in this Drop impl should be enough to shut down the server, system, and thread + // without needing to await anything + + // signal server to stop + let _ = self.server.stop(true); + + // signal system to stop + self.system.stop(); } } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 2d987a131..706a90c00 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.0" -actix-http = "3.0.0-beta.11" -actix-web = { version = "4.0.0-beta.10", default-features = false } +actix-http = "3.0.0-beta.12" +actix-web = { version = "4.0.0-beta.11", default-features = false } bytes = "1" bytestring = "1" @@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.5" +actix-test = "0.1.0-beta.6" -awc = { version = "3.0.0-beta.9", default-features = false } +awc = { version = "3.0.0-beta.10", default-features = false } env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index c04ca435a..a407d00fc 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,9 +23,9 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" actix-macros = "0.2.3" -actix-test = "0.1.0-beta.5" +actix-test = "0.1.0-beta.6" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.10" +actix-web = "4.0.0-beta.11" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5682a237c..6b9531c70 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,9 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.10 - 2021-11-15 + + ## 3.0.0-beta.9 - 2021-10-20 * Updated rustls to v0.20. [#2414] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 6eeb9ce51..ce710d58d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.9" +version = "3.0.0-beta.10" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -55,7 +55,7 @@ __compress = [] [dependencies] actix-codec = "0.4.0" actix-service = "2.0.0" -actix-http = "3.0.0-beta.11" +actix-http = "3.0.0-beta.12" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0-beta.7", features = ["connect"] } actix-utils = "3.0.0" @@ -68,7 +68,7 @@ derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } h2 = "0.3" -http = "0.2" +http = "0.2.5" itoa = "0.4" log =" 0.4" mime = "0.3" @@ -88,13 +88,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.10", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.11", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.12", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } actix-utils = "3.0.0" -actix-server = "2.0.0-beta.3" +actix-server = "2.0.0-beta.9" actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] } -actix-test = { version = "0.1.0-beta.5", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.8" diff --git a/awc/README.md b/awc/README.md index 67bcb9659..96c5ed405 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.9)](https://docs.rs/awc/3.0.0-beta.9) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.10)](https://docs.rs/awc/3.0.0-beta.10) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.9/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.9) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.10/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.10) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs index 97b96fc0a..6bbc9ad07 100644 --- a/awc/src/client/connection.rs +++ b/awc/src/client/connection.rs @@ -173,6 +173,7 @@ impl H2ConnectionInner { /// Cancel spawned connection task on drop. impl Drop for H2ConnectionInner { fn drop(&mut self) { + // TODO: this can end up sending extraneous requests; see if there is a better way to handle if self .sender .send_request(http::Request::new(()), true) @@ -183,8 +184,8 @@ impl Drop for H2ConnectionInner { } } +/// Unified connection type cover HTTP/1 Plain/TLS and HTTP/2 protocols. #[allow(dead_code)] -/// Unified connection type cover Http1 Plain/Tls and Http2 protocols pub enum Connection> where A: ConnectionIo, diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index c075a6090..355fcb6fb 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -19,8 +19,7 @@ use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use rustls::{ client::{ServerCertVerified, ServerCertVerifier}, - Certificate, ClientConfig, OwnedTrustAnchor, PrivateKey, RootCertStore, ServerConfig, - ServerName, + Certificate, ClientConfig, PrivateKey, ServerConfig, ServerName, }; use rustls_pemfile::{certs, pkcs8_private_keys}; diff --git a/examples/on_connect.rs b/examples/on_connect.rs index 24ac86c6b..9709835e6 100644 --- a/examples/on_connect.rs +++ b/examples/on_connect.rs @@ -8,6 +8,7 @@ use std::{any::Any, io, net::SocketAddr}; use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; +#[allow(dead_code)] #[derive(Debug, Clone)] struct ConnectionInfo { bind: SocketAddr, diff --git a/src/dev.rs b/src/dev.rs index be3af86a8..4fac207a7 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -20,7 +20,7 @@ pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, S pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; -pub use actix_server::Server; +pub use actix_server::{Server, ServerHandle}; pub use actix_service::{ always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, }; diff --git a/src/server.rs b/src/server.rs index f15183f85..0f3d7c59a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -159,7 +159,7 @@ where /// /// By default max connections is set to a 25k. pub fn max_connections(mut self, num: usize) -> Self { - self.builder = self.builder.maxconn(num); + self.builder = self.builder.max_concurrent_connections(num); self } @@ -233,7 +233,7 @@ where self } - /// Stop actix system. + /// Stop Actix `System` after server shutdown. pub fn system_exit(mut self) -> Self { self.builder = self.builder.system_exit(); self diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 881c6ce94..887b51d41 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -14,57 +14,45 @@ async fn test_start() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new(); + actix_rt::System::new() + .block_on(async { + let srv = HttpServer::new(|| { + App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), + ) + }) + .workers(1) + .backlog(1) + .max_connections(10) + .max_connection_rate(10) + .keep_alive(10) + .client_timeout(5000) + .client_shutdown(0) + .server_hostname("localhost") + .system_exit() + .disable_signals() + .bind(format!("{}", addr)) + .unwrap() + .run(); - sys.block_on(async { - let srv = HttpServer::new(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), - ) + tx.send(srv.handle()).unwrap(); + + srv.await }) - .workers(1) - .backlog(1) - .max_connections(10) - .max_connection_rate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) - .server_hostname("localhost") - .system_exit() - .disable_signals() - .bind(format!("{}", addr)) - .unwrap() - .run(); - - let _ = tx.send((srv, actix_rt::System::current())); - }); - - let _ = sys.run(); + .unwrap(); }); - let (srv, sys) = rx.recv().unwrap(); - #[cfg(feature = "client")] - { - use actix_http::client; + let srv = rx.recv().unwrap(); - let client = awc::Client::builder() - .connector( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(); + let client = awc::Client::builder() + .connector(awc::Connector::new().timeout(Duration::from_millis(100))) + .finish(); - let host = format!("http://{}", addr); - let response = client.get(host.clone()).send().await.unwrap(); - assert!(response.status().is_success()); - } + let host = format!("http://{}", addr); + let response = client.get(host.clone()).send().await.unwrap(); + assert!(response.status().is_success()); - // stop - let _ = srv.stop(false); - - thread::sleep(Duration::from_millis(100)); - let _ = sys.stop(); + srv.stop(false).await; } #[cfg(feature = "openssl")] @@ -92,37 +80,38 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { #[cfg(feature = "openssl")] async fn test_start_ssl() { use actix_web::HttpRequest; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new(); - let builder = ssl_acceptor(); + actix_rt::System::new() + .block_on(async { + let builder = ssl_acceptor(); - let srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { - assert!(req.app_config().secure()); - HttpResponse::Ok().body("test") - }))) - }) - .workers(1) - .shutdown_timeout(1) - .system_exit() - .disable_signals() - .bind_openssl(format!("{}", addr), builder) - .unwrap(); + let srv = HttpServer::new(|| { + App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { + assert!(req.app_config().secure()); + HttpResponse::Ok().body("test") + }))) + }) + .workers(1) + .shutdown_timeout(1) + .system_exit() + .disable_signals() + .bind_openssl(format!("{}", addr), builder) + .unwrap(); - sys.block_on(async { - let srv = srv.run(); - let _ = tx.send((srv, actix_rt::System::current())); - }); + let srv = srv.run(); + tx.send(srv.handle()).unwrap(); - let _ = sys.run(); + srv.await + }) + .unwrap() }); - let (srv, sys) = rx.recv().unwrap(); + let srv = rx.recv().unwrap(); - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); let _ = builder @@ -141,9 +130,5 @@ async fn test_start_ssl() { let response = client.get(host.clone()).send().await.unwrap(); assert!(response.status().is_success()); - // stop - let _ = srv.stop(false); - - thread::sleep(Duration::from_millis(100)); - let _ = sys.stop(); + srv.stop(false).await; } diff --git a/tests/test_server.rs b/tests/test_server.rs index ff6f5ae5e..d21dac8cf 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -127,6 +127,8 @@ async fn test_body() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -154,6 +156,8 @@ async fn test_body_gzip() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -181,6 +185,8 @@ async fn test_body_gzip2() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -241,6 +247,8 @@ async fn test_body_encoding_override() { e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -275,6 +283,8 @@ async fn test_body_gzip_large() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -314,6 +324,8 @@ async fn test_body_gzip_large_random() { e.read_to_end(&mut dec).unwrap(); assert_eq!(dec.len(), data.len()); assert_eq!(Bytes::from(dec), Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -348,6 +360,8 @@ async fn test_body_chunked_implicit() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -380,6 +394,8 @@ async fn test_body_br_streaming() { let dec = e.finish().unwrap(); println!("T: {:?}", Bytes::copy_from_slice(&dec)); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -401,6 +417,8 @@ async fn test_head_binary() { // read response let bytes = response.body().await.unwrap(); assert!(bytes.is_empty()); + + srv.stop().await; } #[actix_rt::test] @@ -420,6 +438,8 @@ async fn test_no_chunking() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -447,6 +467,8 @@ async fn test_body_deflate() { e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -475,6 +497,8 @@ async fn test_body_brotli() { e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -503,6 +527,8 @@ async fn test_body_zstd() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -534,6 +560,8 @@ async fn test_body_zstd_streaming() { let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -559,6 +587,8 @@ async fn test_zstd_encoding() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -594,6 +624,8 @@ async fn test_zstd_encoding_large() { // read response let bytes = response.body().limit(320_000).await.unwrap(); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -619,6 +651,8 @@ async fn test_encoding() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -644,6 +678,8 @@ async fn test_gzip_encoding() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -670,6 +706,8 @@ async fn test_gzip_encoding_large() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -702,6 +740,8 @@ async fn test_reading_gzip_encoding_large_random() { let bytes = response.body().await.unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -727,6 +767,8 @@ async fn test_reading_deflate_encoding() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -753,6 +795,8 @@ async fn test_reading_deflate_encoding_large() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -785,6 +829,8 @@ async fn test_reading_deflate_encoding_large_random() { let bytes = response.body().await.unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[actix_rt::test] @@ -810,6 +856,8 @@ async fn test_brotli_encoding() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + srv.stop().await; } #[actix_rt::test] @@ -845,6 +893,8 @@ async fn test_brotli_encoding_large() { // read response let bytes = response.body().limit(320_000).await.unwrap(); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[cfg(feature = "openssl")] @@ -861,9 +911,9 @@ async fn test_brotli_encoding_large_openssl() { }); // body - let mut e = BrotliEncoder::new(Vec::new(), 3); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut enc = BrotliEncoder::new(Vec::new(), 3); + enc.write_all(data.as_ref()).unwrap(); + let enc = enc.finish().unwrap(); // client request let mut response = srv @@ -877,6 +927,8 @@ async fn test_brotli_encoding_large_openssl() { // read response let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } #[cfg(feature = "rustls")] @@ -944,6 +996,8 @@ mod plus_rustls { let bytes = response.body().await.unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); + + srv.stop().await; } } @@ -998,6 +1052,8 @@ async fn test_server_cookies() { assert_eq!(cookies[0], second_cookie); assert_eq!(cookies[1], first_cookie); } + + srv.stop().await; } #[actix_rt::test] @@ -1018,6 +1074,8 @@ async fn test_slow_request() { let mut data = String::new(); let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + + srv.stop().await; } #[actix_rt::test] @@ -1030,6 +1088,8 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); + + srv.stop().await } // allow deprecated App::data @@ -1099,4 +1159,6 @@ async fn test_accept_encoding_no_match() { .unwrap(); assert_eq!(response.status().as_u16(), 406); + + srv.stop().await; } From e8a0e168636a439a2eb37323f4acb22be685116a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Nov 2021 18:11:51 +0000 Subject: [PATCH 093/861] run tarpaulin on workspace --- .github/workflows/ci.yml | 2 +- CHANGES.md | 2 +- awc/CHANGES.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff0b9348..a8b21b7fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,7 @@ jobs: if: github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --out Xml --verbose + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - name: Upload to Codecov if: github.ref == 'refs/heads/master' uses: codecov/codecov-action@v1 diff --git a/CHANGES.md b/CHANGES.md index 4ecbd0c2e..0cb5ccb23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,7 @@ ### Changed * Associated type `FromRequest::Config` was removed. [#2233] * Inner field made private on `web::Payload`. [#2384] -* `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403] +* `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] * Updated rustls to v0.20. [#2414] * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 6b9531c70..98998fd5c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,7 @@ ## 3.0.0-beta.10 - 2021-11-15 +* No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 From 4df1cd78b7534cba98aaa930e3a6aaca2ca36ea3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 09:21:10 +0000 Subject: [PATCH 094/861] simplify `AnyBody` and `BodySize` (#2446) --- actix-http/CHANGES.md | 11 +++++++++++ actix-http/src/body/body.rs | 24 +++++++++++------------- actix-http/src/body/message_body.rs | 2 +- actix-http/src/body/mod.rs | 14 +++++++------- actix-http/src/body/size.rs | 12 ++++-------- actix-http/src/encoding/encoder.rs | 3 +-- actix-http/src/h1/dispatcher.rs | 8 ++++---- actix-http/src/h1/encoder.rs | 15 ++++++--------- actix-http/src/h2/dispatcher.rs | 4 +++- actix-http/src/response.rs | 2 +- actix-http/src/response_builder.rs | 4 ++-- awc/src/client/h1proto.rs | 4 ++-- awc/src/client/h2proto.rs | 18 +++++++++--------- awc/src/middleware/redirect.rs | 2 +- awc/src/sender.rs | 2 +- src/response/builder.rs | 4 ++-- src/response/http_codes.rs | 3 +-- 17 files changed, 67 insertions(+), 65 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9ec75b4bc..2beda3dcc 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `AnyBody::empty` for quickly creating an empty body. [#2446] + +### Changed +* Rename `AnyBody::{Message => Stream}`. [#2446] + +### Removed +* `AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +* `BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] + +[#2446]: https://github.com/actix/actix-web/pull/2446 ## 3.0.0-beta.12 - 2021-11-15 diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index cd3e4c5c4..32b464486 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -20,17 +20,19 @@ pub enum AnyBody { /// Empty response. `Content-Length` header is not set. None, - /// Zero sized response body. `Content-Length` header is set to `0`. - Empty, - /// Specific response body. Bytes(Bytes), /// Generic message body. - Message(BoxAnyBody), + Stream(BoxAnyBody), } impl AnyBody { + /// Constructs a new, empty body. + pub fn empty() -> Self { + Self::Bytes(Bytes::new()) + } + /// Create body from slice (copy) pub fn from_slice(s: &[u8]) -> Self { Self::Bytes(Bytes::copy_from_slice(s)) @@ -42,7 +44,7 @@ impl AnyBody { B: MessageBody + 'static, B::Error: Into>, { - Self::Message(BoxAnyBody::from_body(body)) + Self::Stream(BoxAnyBody::from_body(body)) } } @@ -52,9 +54,8 @@ impl MessageBody for AnyBody { fn size(&self) -> BodySize { match self { AnyBody::None => BodySize::None, - AnyBody::Empty => BodySize::Empty, AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - AnyBody::Message(ref body) => body.size(), + AnyBody::Stream(ref body) => body.size(), } } @@ -64,7 +65,6 @@ impl MessageBody for AnyBody { ) -> Poll>> { match self.get_mut() { AnyBody::None => Poll::Ready(None), - AnyBody::Empty => Poll::Ready(None), AnyBody::Bytes(ref mut bin) => { let len = bin.len(); if len == 0 { @@ -74,7 +74,7 @@ impl MessageBody for AnyBody { } } - AnyBody::Message(body) => body + AnyBody::Stream(body) => body .as_pin_mut() .poll_next(cx) .map_err(|err| Error::new_body().with_cause(err)), @@ -86,12 +86,11 @@ impl PartialEq for AnyBody { fn eq(&self, other: &Body) -> bool { match *self { AnyBody::None => matches!(*other, AnyBody::None), - AnyBody::Empty => matches!(*other, AnyBody::Empty), AnyBody::Bytes(ref b) => match *other { AnyBody::Bytes(ref b2) => b == b2, _ => false, }, - AnyBody::Message(_) => false, + AnyBody::Stream(_) => false, } } } @@ -100,9 +99,8 @@ impl fmt::Debug for AnyBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { AnyBody::None => write!(f, "AnyBody::None"), - AnyBody::Empty => write!(f, "AnyBody::Empty"), AnyBody::Bytes(ref b) => write!(f, "AnyBody::Bytes({:?})", b), - AnyBody::Message(_) => write!(f, "AnyBody::Message(_)"), + AnyBody::Stream(_) => write!(f, "AnyBody::Message(_)"), } } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index edb4c550c..62a7e9b1c 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -31,7 +31,7 @@ impl MessageBody for () { type Error = Infallible; fn size(&self) -> BodySize { - BodySize::Empty + BodySize::Sized(0) } fn poll_next( diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index a60a8895c..0d5b0f079 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -33,7 +33,7 @@ pub use self::sized_stream::SizedStream; /// use bytes::Bytes; /// /// # async fn test_to_bytes() { -/// let body = Body::Empty; +/// let body = Body::None; /// let bytes = to_bytes(body).await.unwrap(); /// assert!(bytes.is_empty()); /// @@ -44,8 +44,9 @@ pub use self::sized_stream::SizedStream; /// ``` pub async fn to_bytes(body: B) -> Result { let cap = match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => return Ok(Bytes::new()), + BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()), BodySize::Sized(size) => size as usize, + // good enough first guess for chunk size BodySize::Stream => 32_768, }; @@ -184,7 +185,7 @@ mod tests { #[actix_rt::test] async fn test_unit() { - assert_eq!(().size(), BodySize::Empty); + assert_eq!(().size(), BodySize::Sized(0)); assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) .await .is_none()); @@ -194,11 +195,11 @@ mod tests { async fn test_box_and_pin() { let val = Box::new(()); pin!(val); - assert_eq!(val.size(), BodySize::Empty); + assert_eq!(val.size(), BodySize::Sized(0)); assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); let mut val = Box::pin(()); - assert_eq!(val.size(), BodySize::Empty); + assert_eq!(val.size(), BodySize::Sized(0)); assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); } @@ -214,7 +215,6 @@ mod tests { #[actix_rt::test] async fn test_body_debug() { assert!(format!("{:?}", Body::None).contains("Body::None")); - assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1')); } @@ -252,7 +252,7 @@ mod tests { #[actix_rt::test] async fn test_to_bytes() { - let body = Body::Empty; + let body = Body::empty(); let bytes = to_bytes(body).await.unwrap(); assert!(bytes.is_empty()); diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs index 775d5b8f1..e238eadac 100644 --- a/actix-http/src/body/size.rs +++ b/actix-http/src/body/size.rs @@ -6,14 +6,9 @@ pub enum BodySize { /// Will skip writing Content-Length header. None, - /// Zero size body. - /// - /// Will write `Content-Length: 0` header. - Empty, - /// Known size body. /// - /// Will write `Content-Length: N` header. `Sized(0)` is treated the same as `Empty`. + /// Will write `Content-Length: N` header. Sized(u64), /// Unknown size body. @@ -25,16 +20,17 @@ pub enum BodySize { impl BodySize { /// Returns true if size hint indicates no or empty body. /// + /// Streams will return false because it cannot be known without reading the stream. + /// /// ``` /// # use actix_http::body::BodySize; /// assert!(BodySize::None.is_eof()); - /// assert!(BodySize::Empty.is_eof()); /// assert!(BodySize::Sized(0).is_eof()); /// /// assert!(!BodySize::Sized(64).is_eof()); /// assert!(!BodySize::Stream.is_eof()); /// ``` pub fn is_eof(&self) -> bool { - matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0)) + matches!(self, BodySize::None | BodySize::Sized(0)) } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index abd8cedba..6cb034b76 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -61,7 +61,6 @@ impl Encoder { let body = match body { ResponseBody::Other(b) => match b { Body::None => return ResponseBody::Other(Body::None), - Body::Empty => return ResponseBody::Other(Body::Empty), Body::Bytes(buf) => { if can_encode { EncoderBody::Bytes(buf) @@ -69,7 +68,7 @@ impl Encoder { return ResponseBody::Other(Body::Bytes(buf)); } } - Body::Message(stream) => EncoderBody::BoxedStream(stream), + Body::Stream(stream) => EncoderBody::BoxedStream(stream), }, ResponseBody::Body(stream) => EncoderBody::Stream(stream), }; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 69530ed11..844bc61ea 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -325,7 +325,7 @@ where ) -> Result<(), DispatchError> { let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { - BodySize::None | BodySize::Empty => State::None, + BodySize::None | BodySize::Sized(0) => State::None, _ => State::SendPayload(body), }; self.project().state.set(state); @@ -339,7 +339,7 @@ where ) -> Result<(), DispatchError> { let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { - BodySize::None | BodySize::Empty => State::None, + BodySize::None | BodySize::Sized(0) => State::None, _ => State::SendErrorPayload(body), }; self.project().state.set(state); @@ -380,7 +380,7 @@ where // send_response would update InnerDispatcher state to SendPayload or // None(If response body is empty). // continue loop to poll it. - self.as_mut().send_error_response(res, AnyBody::Empty)?; + self.as_mut().send_error_response(res, AnyBody::empty())?; } // return with upgrade request and poll it exclusively. @@ -772,7 +772,7 @@ where trace!("Slow request timeout"); let _ = self.as_mut().send_error_response( Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), - AnyBody::Empty, + AnyBody::empty(), ); this = self.project(); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index ead14206b..e07c32956 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -93,13 +93,10 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"\r\n"); } } - BodySize::Empty => { - if camel_case { - dst.put_slice(b"\r\nContent-Length: 0\r\n"); - } else { - dst.put_slice(b"\r\ncontent-length: 0\r\n"); - } + BodySize::Sized(0) if camel_case => { + dst.put_slice(b"\r\nContent-Length: 0\r\n") } + BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"), BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::None => dst.put_slice(b"\r\n"), } @@ -336,7 +333,7 @@ impl MessageEncoder { // transfer encoding if !head { self.te = match length { - BodySize::Empty => TransferEncoding::empty(), + BodySize::Sized(0) => TransferEncoding::empty(), BodySize::Sized(len) => TransferEncoding::length(len), BodySize::Stream => { if message.chunked() && !stream { @@ -553,7 +550,7 @@ mod tests { let _ = head.encode_headers( &mut bytes, Version::HTTP_11, - BodySize::Empty, + BodySize::Sized(0), ConnectionType::Close, &ServiceConfig::default(), ); @@ -624,7 +621,7 @@ mod tests { let _ = head.encode_headers( &mut bytes, Version::HTTP_11, - BodySize::Empty, + BodySize::Sized(0), ConnectionType::Close, &ServiceConfig::default(), ); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7326dfff1..8b922b2cd 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -285,9 +285,11 @@ fn prepare_response( let _ = match size { BodySize::None | BodySize::Stream => None, - BodySize::Empty => res + + BodySize::Sized(0) => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(len) => { let mut buf = itoa::Buffer::new(); diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 2aa38c153..47f1c37e2 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -28,7 +28,7 @@ impl Response { pub fn new(status: StatusCode) -> Self { Response { head: BoxedResponseHead::new(status), - body: AnyBody::Empty, + body: AnyBody::empty(), } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index e46d9a28c..a1cb1a423 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -270,7 +270,7 @@ impl ResponseBuilder { /// This `ResponseBuilder` will be left in a useless state. #[inline] pub fn finish(&mut self) -> Response { - self.body(AnyBody::Empty) + self.body(AnyBody::empty()) } /// Create an owned `ResponseBuilder`, leaving the original in a useless state. @@ -390,7 +390,7 @@ mod tests { fn test_content_type() { let resp = Response::build(StatusCode::OK) .content_type("text/plain") - .body(Body::Empty); + .body(Body::empty()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 3c2bb7cc1..7f3ba1b6e 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -70,7 +70,7 @@ where // RFC: https://tools.ietf.org/html/rfc7231#section-5.1.1 let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => { + BodySize::None | BodySize::Sized(0) => { let keep_alive = framed.codec_ref().keepalive(); framed.io_mut().on_release(keep_alive); @@ -104,7 +104,7 @@ where if do_send { // send request body match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} + BodySize::None | BodySize::Sized(0) => {} _ => send_body(body, pin_framed.as_mut()).await?, }; diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs index feb2dbd06..2618e1908 100644 --- a/awc/src/client/h2proto.rs +++ b/awc/src/client/h2proto.rs @@ -36,10 +36,7 @@ where let head_req = head.as_ref().method == Method::HEAD; let length = body.size(); - let eof = matches!( - length, - BodySize::None | BodySize::Empty | BodySize::Sized(0) - ); + let eof = matches!(length, BodySize::None | BodySize::Sized(0)); let mut req = Request::new(()); *req.uri_mut() = head.as_ref().uri.clone(); @@ -52,13 +49,11 @@ where // Content length let _ = match length { BodySize::None => None, - BodySize::Stream => { - skip_len = false; - None - } - BodySize::Empty => req + + BodySize::Sized(0) => req .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(len) => { let mut buf = itoa::Buffer::new(); @@ -67,6 +62,11 @@ where HeaderValue::from_str(buf.format(len)).unwrap(), ) } + + BodySize::Stream => { + skip_len = false; + None + } }; // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 8a79a6596..f01136d14 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -194,7 +194,7 @@ where match body { Some(ref bytes) => Body::Bytes(bytes.clone()), // TODO: should this be Body::Empty or Body::None. - _ => Body::Empty, + _ => Body::empty(), } } else { body = None; diff --git a/awc/src/sender.rs b/awc/src/sender.rs index c0639606e..fcd0c71af 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -297,7 +297,7 @@ impl RequestSender { timeout: Option, config: &ClientConfig, ) -> SendClientRequest { - self.send_body(addr, response_decompress, timeout, config, Body::Empty) + self.send_body(addr, response_decompress, timeout, config, Body::empty()) } fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> diff --git a/src/response/builder.rs b/src/response/builder.rs index 56d30d9d0..f6099a019 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -387,7 +387,7 @@ impl HttpResponseBuilder { /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn finish(&mut self) -> HttpResponse { - self.body(AnyBody::Empty) + self.body(AnyBody::empty()) } /// This method construct new `HttpResponseBuilder` @@ -475,7 +475,7 @@ mod tests { fn test_content_type() { let resp = HttpResponseBuilder::new(StatusCode::OK) .content_type("text/plain") - .body(Body::Empty); + .body(Body::empty()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } diff --git a/src/response/http_codes.rs b/src/response/http_codes.rs index d67ef3f92..44ddb78f9 100644 --- a/src/response/http_codes.rs +++ b/src/response/http_codes.rs @@ -87,13 +87,12 @@ impl HttpResponse { #[cfg(test)] mod tests { - use crate::dev::Body; use crate::http::StatusCode; use crate::HttpResponse; #[test] fn test_build() { - let resp = HttpResponse::Ok().body(Body::Empty); + let resp = HttpResponse::Ok().finish(); assert_eq!(resp.status(), StatusCode::OK); } } From 13cf5a9e445b31a6b418bc3fb47555b1e45aa5c3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 16:55:45 +0000 Subject: [PATCH 095/861] remove chunked encoding header for websockets --- actix-http/src/ws/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 7df924cf5..70e0e62a2 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -210,7 +210,6 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { Response::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header(( header::SEC_WEBSOCKET_ACCEPT, // key is known to be header value safe ascii From d8cbb879dde831f5b6083664660e90ab1ecc584d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 21:41:35 +0000 Subject: [PATCH 096/861] make `AnyBody` generic on `Body` type (#2448) --- CHANGES.md | 10 ++ actix-http/CHANGES.md | 17 ++- actix-http/Cargo.toml | 1 + actix-http/src/body/body.rs | 174 ++++++++++++++++++++------- actix-http/src/body/body_stream.rs | 36 ++++++ actix-http/src/body/mod.rs | 8 +- actix-http/src/body/response_body.rs | 84 ------------- actix-http/src/body/sized_stream.rs | 45 +++++++ actix-http/src/encoding/encoder.rs | 39 +++--- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/response_builder.rs | 2 +- awc/src/sender.rs | 4 +- src/dev.rs | 2 +- src/middleware/compat.rs | 4 +- src/middleware/compress.rs | 31 +++-- src/responder.rs | 27 +---- src/response/builder.rs | 4 +- src/response/response.rs | 3 + 18 files changed, 283 insertions(+), 210 deletions(-) delete mode 100644 actix-http/src/body/response_body.rs diff --git a/CHANGES.md b/CHANGES.md index 0cb5ccb23..784500d9e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Compress middleware's response type is now `AnyBody>`. [#2448] + +### Fixed +* Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] + +### Removed +* `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] + +[#2423]: https://github.com/actix/actix-web/pull/2423 ## 4.0.0-beta.11 - 2021-11-15 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2beda3dcc..71cdd6d4c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,14 +2,23 @@ ## Unreleased - 2021-xx-xx ### Added -* `AnyBody::empty` for quickly creating an empty body. [#2446] +* `body::AnyBody::empty` for quickly creating an empty body. [#2446] +* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -* Rename `AnyBody::{Message => Stream}`. [#2446] +* Rename `body::AnyBody::{Message => Body}`. [#2446] +* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +* `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -* `AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -* `BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +* `EncoderError::Boxed`; it is no longer required. [#2446] +* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 784312445..27a147379 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -92,6 +92,7 @@ regex = "1.3" rustls-pemfile = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.2", features = ["net", "rt"] } diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 32b464486..d51173a57 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -8,6 +8,7 @@ use std::{ use bytes::{Bytes, BytesMut}; use futures_core::Stream; +use pin_project::pin_project; use crate::error::Error; @@ -16,15 +17,17 @@ use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream}; pub type Body = AnyBody; /// Represents various types of HTTP message body. -pub enum AnyBody { +#[pin_project(project = AnyBodyProj)] +#[derive(Clone)] +pub enum AnyBody { /// Empty response. `Content-Length` header is not set. None, - /// Specific response body. + /// Complete, in-memory response body. Bytes(Bytes), - /// Generic message body. - Stream(BoxAnyBody), + /// Generic / Other message body. + Body(#[pin] B), } impl AnyBody { @@ -33,29 +36,60 @@ impl AnyBody { Self::Bytes(Bytes::new()) } - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Self { - Self::Bytes(Bytes::copy_from_slice(s)) - } - - /// Create body from generic message body. - pub fn from_message(body: B) -> Self + /// Create boxed body from generic message body. + pub fn new_boxed(body: B) -> Self where B: MessageBody + 'static, B::Error: Into>, { - Self::Stream(BoxAnyBody::from_body(body)) + Self::Body(BoxBody::from_body(body)) + } + + /// Constructs new `AnyBody` instance from a slice of bytes by copying it. + /// + /// If your bytes container is owned, it may be cheaper to use a `From` impl. + pub fn copy_from_slice(s: &[u8]) -> Self { + Self::Bytes(Bytes::copy_from_slice(s)) + } + + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")] + pub fn from_slice(s: &[u8]) -> Self { + Self::Bytes(Bytes::copy_from_slice(s)) } } -impl MessageBody for AnyBody { +impl AnyBody +where + B: MessageBody + 'static, + B::Error: Into>, +{ + /// Create body from generic message body. + pub fn new(body: B) -> Self { + Self::Body(body) + } + + pub fn into_boxed(self) -> AnyBody { + match self { + Self::None => AnyBody::None, + Self::Bytes(bytes) => AnyBody::Bytes(bytes), + Self::Body(body) => AnyBody::new_boxed(body), + } + } +} + +impl MessageBody for AnyBody +where + B: MessageBody, + B::Error: Into> + 'static, +{ type Error = Error; fn size(&self) -> BodySize { match self { AnyBody::None => BodySize::None, AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - AnyBody::Stream(ref body) => body.size(), + AnyBody::Body(ref body) => body.size(), } } @@ -63,9 +97,9 @@ impl MessageBody for AnyBody { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - match self.get_mut() { - AnyBody::None => Poll::Ready(None), - AnyBody::Bytes(ref mut bin) => { + match self.project() { + AnyBodyProj::None => Poll::Ready(None), + AnyBodyProj::Bytes(bin) => { let len = bin.len(); if len == 0 { Poll::Ready(None) @@ -74,8 +108,7 @@ impl MessageBody for AnyBody { } } - AnyBody::Stream(body) => body - .as_pin_mut() + AnyBodyProj::Body(body) => body .poll_next(cx) .map_err(|err| Error::new_body().with_cause(err)), } @@ -90,30 +123,30 @@ impl PartialEq for AnyBody { AnyBody::Bytes(ref b2) => b == b2, _ => false, }, - AnyBody::Stream(_) => false, + AnyBody::Body(_) => false, } } } -impl fmt::Debug for AnyBody { +impl fmt::Debug for AnyBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { AnyBody::None => write!(f, "AnyBody::None"), - AnyBody::Bytes(ref b) => write!(f, "AnyBody::Bytes({:?})", b), - AnyBody::Stream(_) => write!(f, "AnyBody::Message(_)"), + AnyBody::Bytes(ref bytes) => write!(f, "AnyBody::Bytes({:?})", bytes), + AnyBody::Body(ref stream) => write!(f, "AnyBody::Message({:?})", stream), } } } impl From<&'static str> for AnyBody { - fn from(s: &'static str) -> Body { - AnyBody::Bytes(Bytes::from_static(s.as_ref())) + fn from(string: &'static str) -> Body { + AnyBody::Bytes(Bytes::from_static(string.as_ref())) } } impl From<&'static [u8]> for AnyBody { - fn from(s: &'static [u8]) -> Body { - AnyBody::Bytes(Bytes::from_static(s)) + fn from(bytes: &'static [u8]) -> Body { + AnyBody::Bytes(Bytes::from_static(bytes)) } } @@ -124,20 +157,20 @@ impl From> for AnyBody { } impl From for AnyBody { - fn from(s: String) -> Body { - s.into_bytes().into() + fn from(string: String) -> Body { + string.into_bytes().into() } } impl From<&'_ String> for AnyBody { - fn from(s: &String) -> Body { - AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) + fn from(string: &String) -> Body { + AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string))) } } impl From> for AnyBody { - fn from(s: Cow<'_, str>) -> Body { - match s { + fn from(string: Cow<'_, str>) -> Body { + match string { Cow::Owned(s) => AnyBody::from(s), Cow::Borrowed(s) => { AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) @@ -147,14 +180,14 @@ impl From> for AnyBody { } impl From for AnyBody { - fn from(s: Bytes) -> Body { - AnyBody::Bytes(s) + fn from(bytes: Bytes) -> Body { + AnyBody::Bytes(bytes) } } impl From for AnyBody { - fn from(s: BytesMut) -> Body { - AnyBody::Bytes(s.freeze()) + fn from(bytes: BytesMut) -> Body { + AnyBody::Bytes(bytes.freeze()) } } @@ -163,8 +196,8 @@ where S: Stream> + 'static, E: Into> + 'static, { - fn from(s: SizedStream) -> Body { - AnyBody::from_message(s) + fn from(stream: SizedStream) -> Body { + AnyBody::new_boxed(stream) } } @@ -173,15 +206,15 @@ where S: Stream> + 'static, E: Into> + 'static, { - fn from(s: BodyStream) -> Body { - AnyBody::from_message(s) + fn from(stream: BodyStream) -> Body { + AnyBody::new_boxed(stream) } } /// A boxed message body with boxed errors. -pub struct BoxAnyBody(Pin>>>); +pub struct BoxBody(Pin>>>); -impl BoxAnyBody { +impl BoxBody { /// Boxes a `MessageBody` and any errors it generates. pub fn from_body(body: B) -> Self where @@ -195,18 +228,18 @@ impl BoxAnyBody { /// Returns a mutable pinned reference to the inner message body type. pub fn as_pin_mut( &mut self, - ) -> Pin<&mut (dyn MessageBody>)> { + ) -> Pin<&mut (dyn MessageBody>)> { self.0.as_mut() } } -impl fmt::Debug for BoxAnyBody { +impl fmt::Debug for BoxBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("BoxAnyBody(dyn MessageBody)") } } -impl MessageBody for BoxAnyBody { +impl MessageBody for BoxBody { type Error = Error; fn size(&self) -> BodySize { @@ -223,3 +256,52 @@ impl MessageBody for BoxAnyBody { .map_err(|err| Error::new_body().with_cause(err)) } } + +#[cfg(test)] +mod tests { + use std::marker::PhantomPinned; + + use static_assertions::{assert_impl_all, assert_not_impl_all}; + + use super::*; + use crate::body::to_bytes; + + struct PinType(PhantomPinned); + + impl MessageBody for PinType { + type Error = crate::Error; + + fn size(&self) -> BodySize { + unimplemented!() + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + unimplemented!() + } + } + + assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody>: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin); + assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin); + assert_impl_all!(AnyBody: MessageBody); + + assert_not_impl_all!(AnyBody: Send, Sync, Unpin); + assert_not_impl_all!(BoxBody: Send, Sync, Unpin); + assert_not_impl_all!(AnyBody: Send, Sync, Unpin); + + #[actix_rt::test] + async fn nested_boxed_body() { + let body = AnyBody::copy_from_slice(&[1, 2, 3]); + let boxed_body = BoxBody::from_body(BoxBody::from_body(body)); + + assert_eq!( + to_bytes(boxed_body).await.unwrap(), + Bytes::from(vec![1, 2, 3]), + ); + } +} diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index f726f4475..31de9b48f 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -75,10 +75,22 @@ mod tests { use derive_more::{Display, Error}; use futures_core::ready; use futures_util::{stream, FutureExt as _}; + use static_assertions::{assert_impl_all, assert_not_impl_all}; use super::*; use crate::body::to_bytes; + assert_impl_all!(BodyStream>>: MessageBody); + assert_impl_all!(BodyStream>>: MessageBody); + assert_impl_all!(BodyStream>>: MessageBody); + assert_impl_all!(BodyStream>>: MessageBody); + assert_impl_all!(BodyStream>>: MessageBody); + + assert_not_impl_all!(BodyStream>: MessageBody); + assert_not_impl_all!(BodyStream>: MessageBody); + // crate::Error is not Clone + assert_not_impl_all!(BodyStream>>: MessageBody); + #[actix_rt::test] async fn skips_empty_chunks() { let body = BodyStream::new(stream::iter( @@ -124,6 +136,30 @@ mod tests { assert!(matches!(to_bytes(body).await, Err(StreamErr))); } + #[actix_rt::test] + async fn stream_string_error() { + // `&'static str` does not impl `Error` + // but it does impl `Into>` + + let body = BodyStream::new(stream::once(async { Err("stringy error") })); + assert!(matches!(to_bytes(body).await, Err("stringy error"))); + } + + #[actix_rt::test] + async fn stream_boxed_error() { + // `Box` does not impl `Error` + // but it does impl `Into>` + + let body = BodyStream::new(stream::once(async { + Err(Box::::from("stringy error")) + })); + + assert_eq!( + to_bytes(body).await.unwrap_err().to_string(), + "stringy error" + ); + } + #[actix_rt::test] async fn stream_delayed_error() { let body = diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 0d5b0f079..07e5e67ce 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -11,15 +11,13 @@ use futures_core::ready; mod body; mod body_stream; mod message_body; -mod response_body; mod size; mod sized_stream; -pub use self::body::{AnyBody, Body, BoxAnyBody}; +pub use self::body::{AnyBody, Body, BoxBody}; pub use self::body_stream::BodyStream; pub use self::message_body::MessageBody; pub(crate) use self::message_body::MessageBodyMapErr; -pub use self::response_body::ResponseBody; pub use self::size::BodySize; pub use self::sized_stream::SizedStream; @@ -108,10 +106,10 @@ mod tests { assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( - Body::from_slice(b"test".as_ref()).size(), + Body::copy_from_slice(b"test".as_ref()).size(), BodySize::Sized(4) ); - assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); + assert_eq!(Body::copy_from_slice(b"test".as_ref()).get_ref(), b"test"); let sb = Bytes::from(&b"test"[..]); pin!(sb); diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs deleted file mode 100644 index 699ea9384..000000000 --- a/actix-http/src/body/response_body.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{ - mem, - pin::Pin, - task::{Context, Poll}, -}; - -use bytes::Bytes; -use futures_core::Stream; -use pin_project::pin_project; - -use crate::error::Error; - -use super::{Body, BodySize, MessageBody}; - -#[pin_project(project = ResponseBodyProj)] -pub enum ResponseBody { - Body(#[pin] B), - Other(Body), -} - -impl ResponseBody { - pub fn into_body(self) -> ResponseBody { - match self { - ResponseBody::Body(b) => ResponseBody::Other(b), - ResponseBody::Other(b) => ResponseBody::Other(b), - } - } -} - -impl ResponseBody { - pub fn take_body(&mut self) -> ResponseBody { - mem::replace(self, ResponseBody::Other(Body::None)) - } -} - -impl ResponseBody { - pub fn as_ref(&self) -> Option<&B> { - if let ResponseBody::Body(ref b) = self { - Some(b) - } else { - None - } - } -} - -impl MessageBody for ResponseBody -where - B: MessageBody, - B::Error: Into, -{ - type Error = Error; - - fn size(&self) -> BodySize { - match self { - ResponseBody::Body(ref body) => body.size(), - ResponseBody::Other(ref body) => body.size(), - } - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - Stream::poll_next(self, cx) - } -} - -impl Stream for ResponseBody -where - B: MessageBody, - B::Error: Into, -{ - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx).map_err(Into::into), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } - } -} diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index b6ceb32fe..b92de44cc 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -72,10 +72,22 @@ mod tests { use actix_rt::pin; use actix_utils::future::poll_fn; use futures_util::stream; + use static_assertions::{assert_impl_all, assert_not_impl_all}; use super::*; use crate::body::to_bytes; + assert_impl_all!(SizedStream>>: MessageBody); + assert_impl_all!(SizedStream>>: MessageBody); + assert_impl_all!(SizedStream>>: MessageBody); + assert_impl_all!(SizedStream>>: MessageBody); + assert_impl_all!(SizedStream>>: MessageBody); + + assert_not_impl_all!(SizedStream>: MessageBody); + assert_not_impl_all!(SizedStream>: MessageBody); + // crate::Error is not Clone + assert_not_impl_all!(SizedStream>>: MessageBody); + #[actix_rt::test] async fn skips_empty_chunks() { let body = SizedStream::new( @@ -119,4 +131,37 @@ mod tests { assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); } + + #[actix_rt::test] + async fn stream_string_error() { + // `&'static str` does not impl `Error` + // but it does impl `Into>` + + let body = SizedStream::new(0, stream::once(async { Err("stringy error") })); + assert_eq!(to_bytes(body).await, Ok(Bytes::new())); + + let body = SizedStream::new(1, stream::once(async { Err("stringy error") })); + assert!(matches!(to_bytes(body).await, Err("stringy error"))); + } + + #[actix_rt::test] + async fn stream_boxed_error() { + // `Box` does not impl `Error` + // but it does impl `Into>` + + let body = SizedStream::new( + 0, + stream::once(async { Err(Box::::from("stringy error")) }), + ); + assert_eq!(to_bytes(body).await.unwrap(), Bytes::new()); + + let body = SizedStream::new( + 1, + stream::once(async { Err(Box::::from("stringy error")) }), + ); + assert_eq!( + to_bytes(body).await.unwrap_err().to_string(), + "stringy error" + ); + } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 6cb034b76..62100ff1d 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -24,7 +24,7 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use zstd::stream::write::Encoder as ZstdEncoder; use crate::{ - body::{Body, BodySize, BoxAnyBody, MessageBody, ResponseBody}, + body::{AnyBody, BodySize, MessageBody}, http::{ header::{ContentEncoding, CONTENT_ENCODING}, HeaderValue, StatusCode, @@ -50,8 +50,8 @@ impl Encoder { pub fn response( encoding: ContentEncoding, head: &mut ResponseHead, - body: ResponseBody, - ) -> ResponseBody> { + body: AnyBody, + ) -> AnyBody> { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT @@ -59,18 +59,15 @@ impl Encoder { || encoding == ContentEncoding::Auto); let body = match body { - ResponseBody::Other(b) => match b { - Body::None => return ResponseBody::Other(Body::None), - Body::Bytes(buf) => { - if can_encode { - EncoderBody::Bytes(buf) - } else { - return ResponseBody::Other(Body::Bytes(buf)); - } + AnyBody::None => return AnyBody::None, + AnyBody::Bytes(buf) => { + if can_encode { + EncoderBody::Bytes(buf) + } else { + return AnyBody::Bytes(buf); } - Body::Stream(stream) => EncoderBody::BoxedStream(stream), - }, - ResponseBody::Body(stream) => EncoderBody::Stream(stream), + } + AnyBody::Body(body) => EncoderBody::Stream(body), }; if can_encode { @@ -78,7 +75,8 @@ impl Encoder { if let Some(enc) = ContentEncoder::encoder(encoding) { update_head(encoding, head); head.no_chunking(false); - return ResponseBody::Body(Encoder { + + return AnyBody::Body(Encoder { body, eof: false, fut: None, @@ -87,7 +85,7 @@ impl Encoder { } } - ResponseBody::Body(Encoder { + AnyBody::Body(Encoder { body, eof: false, fut: None, @@ -100,7 +98,6 @@ impl Encoder { enum EncoderBody { Bytes(Bytes), Stream(#[pin] B), - BoxedStream(BoxAnyBody), } impl MessageBody for EncoderBody @@ -113,7 +110,6 @@ where match self { EncoderBody::Bytes(ref b) => b.size(), EncoderBody::Stream(ref b) => b.size(), - EncoderBody::BoxedStream(ref b) => b.size(), } } @@ -130,9 +126,6 @@ where } } EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body), - EncoderBodyProj::BoxedStream(ref mut b) => { - b.as_pin_mut().poll_next(cx).map_err(EncoderError::Boxed) - } } } } @@ -348,9 +341,6 @@ pub enum EncoderError { #[display(fmt = "body")] Body(E), - #[display(fmt = "boxed")] - Boxed(Box), - #[display(fmt = "blocking")] Blocking(BlockingError), @@ -362,7 +352,6 @@ impl StdError for EncoderError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { EncoderError::Body(err) => Some(err), - EncoderError::Boxed(err) => Some(&**err), EncoderError::Blocking(err) => Some(err), EncoderError::Io(err) => Some(err), } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 844bc61ea..163d84f5b 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1077,7 +1077,7 @@ mod tests { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>( - Response::ok().set_body(AnyBody::from_slice(path)), + Response::ok().set_body(AnyBody::copy_from_slice(path)), )) }) } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index a1cb1a423..e934f94dc 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -262,7 +262,7 @@ impl ResponseBuilder { S: Stream> + 'static, E: Into> + 'static, { - self.body(AnyBody::from_message(BodyStream::new(stream))) + self.body(AnyBody::new_boxed(BodyStream::new(stream))) } /// Generate response with an empty body. diff --git a/awc/src/sender.rs b/awc/src/sender.rs index fcd0c71af..02870aea9 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -9,7 +9,7 @@ use std::{ }; use actix_http::{ - body::{Body, BodyStream}, + body::{AnyBody, Body, BodyStream}, http::{ header::{self, HeaderMap, HeaderName, IntoHeaderValue}, Error as HttpError, @@ -286,7 +286,7 @@ impl RequestSender { response_decompress, timeout, config, - Body::from_message(BodyStream::new(stream)), + AnyBody::new_boxed(BodyStream::new(stream)), ) } diff --git a/src/dev.rs b/src/dev.rs index 4fac207a7..27c206e70 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -14,7 +14,7 @@ pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; -pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream}; +pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream}; #[cfg(feature = "__compress")] pub use actix_http::encoding::Decoder as Decompress; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 0a6256fe2..752e90f94 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::body::{Body, MessageBody}; +use actix_http::body::{AnyBody, MessageBody}; use actix_service::{Service, Transform}; use futures_core::{future::LocalBoxFuture, ready}; @@ -124,7 +124,7 @@ where B::Error: Into>, { fn map_body(self) -> ServiceResponse { - self.map_body(|_, body| Body::from_message(body)) + self.map_body(|_, body| AnyBody::new_boxed(body)) } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 4854f4beb..3e85cb846 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -10,13 +10,14 @@ use std::{ }; use actix_http::{ - body::{MessageBody, ResponseBody}, + body::{AnyBody, MessageBody}, encoding::Encoder, http::header::{ContentEncoding, ACCEPT_ENCODING}, StatusCode, }; use actix_service::{Service, Transform}; use actix_utils::future::{ok, Either, Ready}; +use bytes::Bytes; use futures_core::ready; use once_cell::sync::Lazy; use pin_project::pin_project; @@ -61,7 +62,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Response = ServiceResponse>>; + type Response = ServiceResponse>>; type Error = Error; type Transform = CompressMiddleware; type InitError = (); @@ -110,7 +111,7 @@ where S: Service, Error = Error>, B: MessageBody, { - type Response = ServiceResponse>>; + type Response = ServiceResponse>>; type Error = Error; type Future = Either, Ready>>; @@ -142,15 +143,19 @@ where // There is an HTTP header but we cannot match what client as asked for Some(Err(_)) => { - let res = HttpResponse::with_body( - StatusCode::NOT_ACCEPTABLE, - SUPPORTED_ALGORITHM_NAMES.as_str(), - ); - let enc = ContentEncoding::Identity; + let res = HttpResponse::new(StatusCode::NOT_ACCEPTABLE); - Either::right(ok(req.into_response(res.map_body(move |head, body| { - Encoder::response(enc, head, ResponseBody::Other(body.into())) - })))) + let res: HttpResponse>> = res.map_body(move |head, _| { + let body_bytes = Bytes::from(SUPPORTED_ALGORITHM_NAMES.as_bytes()); + + Encoder::response( + ContentEncoding::Identity, + head, + AnyBody::Bytes(body_bytes), + ) + }); + + Either::right(ok(req.into_response(res))) } } } @@ -172,7 +177,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Output = Result>>, Error>; + type Output = Result>>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); @@ -186,7 +191,7 @@ where }; Poll::Ready(Ok(resp.map_body(move |head, body| { - Encoder::response(enc, head, ResponseBody::Body(body)) + Encoder::response(enc, head, AnyBody::Body(body)) }))) } Err(e) => Poll::Ready(Err(e)), diff --git a/src/responder.rs b/src/responder.rs index 005bff03e..4d2e97c36 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -232,7 +232,7 @@ pub(crate) mod tests { use bytes::{Bytes, BytesMut}; use super::*; - use crate::dev::{Body, ResponseBody}; + use crate::dev::AnyBody; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::test::{init_service, TestRequest}; use crate::{error, web, App}; @@ -264,13 +264,13 @@ pub(crate) mod tests { pub(crate) trait BodyTest { fn bin_ref(&self) -> &[u8]; - fn body(&self) -> &Body; + fn body(&self) -> &AnyBody; } impl BodyTest for Body { fn bin_ref(&self) -> &[u8] { match self { - Body::Bytes(ref bin) => bin, + AnyBody::Bytes(ref bin) => bin, _ => unreachable!("bug in test impl"), } } @@ -279,27 +279,6 @@ pub(crate) mod tests { } } - impl BodyTest for ResponseBody { - fn bin_ref(&self) -> &[u8] { - match self { - ResponseBody::Body(ref b) => match b { - Body::Bytes(ref bin) => bin, - _ => unreachable!("bug in test impl"), - }, - ResponseBody::Other(ref b) => match b { - Body::Bytes(ref bin) => bin, - _ => unreachable!("bug in test impl"), - }, - } - } - fn body(&self) -> &Body { - match self { - ResponseBody::Body(ref b) => b, - ResponseBody::Other(ref b) => b, - } - } - } - #[actix_rt::test] async fn test_responder() { let req = TestRequest::default().to_http_request(); diff --git a/src/response/builder.rs b/src/response/builder.rs index f6099a019..e42d85f59 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -354,10 +354,10 @@ impl HttpResponseBuilder { #[inline] pub fn streaming(&mut self, stream: S) -> HttpResponse where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into> + 'static, { - self.body(AnyBody::from_message(BodyStream::new(stream))) + self.body(AnyBody::new_boxed(BodyStream::new(stream))) } /// Set a json body and generate `Response` diff --git a/src/response/response.rs b/src/response/response.rs index 09515c839..46360e536 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -227,6 +227,9 @@ impl HttpResponse { } } + // TODO: into_body equivalent + // TODO: into_boxed_body + /// Extract response body pub fn into_body(self) -> B { self.res.into_body() From 668a33c793a41fd8cedb7170f4930fa543f25d74 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 22:10:30 +0000 Subject: [PATCH 097/861] remove internal usage of Body --- actix-http/examples/echo2.rs | 4 +- actix-http/src/body/body.rs | 43 +++++++++---- actix-http/src/body/mod.rs | 68 +++++++++++--------- actix-http/src/error.rs | 25 +++---- actix-http/src/header/shared/quality_item.rs | 1 + actix-http/src/response_builder.rs | 6 +- actix-http/tests/test_openssl.rs | 4 +- actix-http/tests/test_rustls.rs | 6 +- actix-http/tests/test_server.rs | 4 +- awc/src/connect.rs | 4 +- awc/src/frozen.rs | 6 +- awc/src/middleware/redirect.rs | 12 ++-- awc/src/request.rs | 4 +- awc/src/sender.rs | 10 +-- src/app.rs | 4 +- src/dev.rs | 1 + src/error/internal.rs | 4 +- src/responder.rs | 10 +-- src/response/builder.rs | 4 +- src/response/response.rs | 12 ++-- src/scope.rs | 8 +-- src/test.rs | 6 +- tests/test_server.rs | 2 +- 23 files changed, 137 insertions(+), 111 deletions(-) diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index db195d65b..6e5ddec7c 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,12 +1,12 @@ use std::io; -use actix_http::{body::Body, http::HeaderValue, http::StatusCode}; +use actix_http::{body::AnyBody, http::HeaderValue, http::StatusCode}; use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; -async fn handle_request(mut req: Request) -> Result, Error> { +async fn handle_request(mut req: Request) -> Result, Error> { let mut body = BytesMut::new(); while let Some(item) = req.payload().next().await { body.extend_from_slice(&item?) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index d51173a57..1d88777bc 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -14,6 +14,7 @@ use crate::error::Error; use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream}; +#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")] pub type Body = AnyBody; /// Represents various types of HTTP message body. @@ -116,7 +117,7 @@ where } impl PartialEq for AnyBody { - fn eq(&self, other: &Body) -> bool { + fn eq(&self, other: &AnyBody) -> bool { match *self { AnyBody::None => matches!(*other, AnyBody::None), AnyBody::Bytes(ref b) => match *other { @@ -139,37 +140,37 @@ impl fmt::Debug for AnyBody { } impl From<&'static str> for AnyBody { - fn from(string: &'static str) -> Body { + fn from(string: &'static str) -> AnyBody { AnyBody::Bytes(Bytes::from_static(string.as_ref())) } } impl From<&'static [u8]> for AnyBody { - fn from(bytes: &'static [u8]) -> Body { + fn from(bytes: &'static [u8]) -> AnyBody { AnyBody::Bytes(Bytes::from_static(bytes)) } } impl From> for AnyBody { - fn from(vec: Vec) -> Body { + fn from(vec: Vec) -> AnyBody { AnyBody::Bytes(Bytes::from(vec)) } } impl From for AnyBody { - fn from(string: String) -> Body { + fn from(string: String) -> AnyBody { string.into_bytes().into() } } impl From<&'_ String> for AnyBody { - fn from(string: &String) -> Body { + fn from(string: &String) -> AnyBody { AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string))) } } impl From> for AnyBody { - fn from(string: Cow<'_, str>) -> Body { + fn from(string: Cow<'_, str>) -> AnyBody { match string { Cow::Owned(s) => AnyBody::from(s), Cow::Borrowed(s) => { @@ -180,33 +181,53 @@ impl From> for AnyBody { } impl From for AnyBody { - fn from(bytes: Bytes) -> Body { + fn from(bytes: Bytes) -> Self { AnyBody::Bytes(bytes) } } impl From for AnyBody { - fn from(bytes: BytesMut) -> Body { + fn from(bytes: BytesMut) -> Self { AnyBody::Bytes(bytes.freeze()) } } +impl From> for AnyBody> +where + S: Stream> + 'static, + E: Into> + 'static, +{ + fn from(stream: SizedStream) -> Self { + AnyBody::new(stream) + } +} + impl From> for AnyBody where S: Stream> + 'static, E: Into> + 'static, { - fn from(stream: SizedStream) -> Body { + fn from(stream: SizedStream) -> Self { AnyBody::new_boxed(stream) } } +impl From> for AnyBody> +where + S: Stream> + 'static, + E: Into> + 'static, +{ + fn from(stream: BodyStream) -> Self { + AnyBody::new(stream) + } +} + impl From> for AnyBody where S: Stream> + 'static, E: Into> + 'static, { - fn from(stream: BodyStream) -> Body { + fn from(stream: BodyStream) -> Self { AnyBody::new_boxed(stream) } } diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 07e5e67ce..83299a471 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -14,6 +14,7 @@ mod message_body; mod size; mod sized_stream; +#[allow(deprecated)] pub use self::body::{AnyBody, Body, BoxBody}; pub use self::body_stream::BodyStream; pub use self::message_body::MessageBody; @@ -76,10 +77,10 @@ mod tests { use super::*; - impl Body { + impl AnyBody { pub(crate) fn get_ref(&self) -> &[u8] { match *self { - Body::Bytes(ref bin) => bin, + AnyBody::Bytes(ref bin) => bin, _ => panic!(), } } @@ -87,9 +88,9 @@ mod tests { #[actix_rt::test] async fn test_static_str() { - assert_eq!(Body::from("").size(), BodySize::Sized(0)); - assert_eq!(Body::from("test").size(), BodySize::Sized(4)); - assert_eq!(Body::from("test").get_ref(), b"test"); + assert_eq!(AnyBody::from("").size(), BodySize::Sized(0)); + assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from("test").get_ref(), b"test"); assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!( @@ -103,13 +104,16 @@ mod tests { #[actix_rt::test] async fn test_static_bytes() { - assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( - Body::copy_from_slice(b"test".as_ref()).size(), + AnyBody::copy_from_slice(b"test".as_ref()).size(), BodySize::Sized(4) ); - assert_eq!(Body::copy_from_slice(b"test".as_ref()).get_ref(), b"test"); + assert_eq!( + AnyBody::copy_from_slice(b"test".as_ref()).get_ref(), + b"test" + ); let sb = Bytes::from(&b"test"[..]); pin!(sb); @@ -122,8 +126,8 @@ mod tests { #[actix_rt::test] async fn test_vec() { - assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); + assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test"); let test_vec = Vec::from("test"); pin!(test_vec); @@ -140,8 +144,8 @@ mod tests { #[actix_rt::test] async fn test_bytes() { let b = Bytes::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -154,8 +158,8 @@ mod tests { #[actix_rt::test] async fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -168,10 +172,10 @@ mod tests { #[actix_rt::test] async fn test_string() { let b = "test".to_owned(); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); - assert_eq!(Body::from(&b).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(&b).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -204,29 +208,33 @@ mod tests { #[actix_rt::test] async fn test_body_eq() { assert!( - Body::Bytes(Bytes::from_static(b"1")) - == Body::Bytes(Bytes::from_static(b"1")) + AnyBody::Bytes(Bytes::from_static(b"1")) + == AnyBody::Bytes(Bytes::from_static(b"1")) ); - assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); + assert!(AnyBody::Bytes(Bytes::from_static(b"1")) != AnyBody::None); } #[actix_rt::test] async fn test_body_debug() { - assert!(format!("{:?}", Body::None).contains("Body::None")); - assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1')); + assert!(format!("{:?}", AnyBody::::None).contains("Body::None")); + assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1')); } #[actix_rt::test] async fn test_serde_json() { use serde_json::{json, Value}; assert_eq!( - Body::from(serde_json::to_vec(&Value::String("test".to_owned())).unwrap()) - .size(), + AnyBody::from( + serde_json::to_vec(&Value::String("test".to_owned())).unwrap() + ) + .size(), BodySize::Sized(6) ); assert_eq!( - Body::from(serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()) - .size(), + AnyBody::from( + serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap() + ) + .size(), BodySize::Sized(25) ); } @@ -250,11 +258,11 @@ mod tests { #[actix_rt::test] async fn test_to_bytes() { - let body = Body::empty(); + let body = AnyBody::empty(); let bytes = to_bytes(body).await.unwrap(); assert!(bytes.is_empty()); - let body = Body::Bytes(Bytes::from_static(b"123")); + let body = AnyBody::copy_from_slice(b"123"); let bytes = to_bytes(body).await.unwrap(); assert_eq!(bytes, b"123"[..]); } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index f7d7f696a..c7c0cce0e 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -5,10 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err use derive_more::{Display, Error, From}; use http::{uri::InvalidUri, StatusCode}; -use crate::{ - body::{AnyBody, Body}, - ws, Response, -}; +use crate::{body::AnyBody, ws, Response}; pub use http::Error as HttpError; @@ -29,6 +26,11 @@ impl Error { } } + pub(crate) fn with_cause(mut self, cause: impl Into>) -> Self { + self.inner.cause = Some(cause.into()); + self + } + pub(crate) fn new_http() -> Self { Self::new(Kind::Http) } @@ -49,14 +51,12 @@ impl Error { Self::new(Kind::SendResponse) } - // TODO: remove allow - #[allow(dead_code)] + #[allow(unused)] // reserved for future use (TODO: remove allow when being used) pub(crate) fn new_io() -> Self { Self::new(Kind::Io) } - // used in encoder behind feature flag so ignore unused warning - #[allow(unused)] + #[allow(unused)] // used in encoder behind feature flag so ignore unused warning pub(crate) fn new_encoder() -> Self { Self::new(Kind::Encoder) } @@ -64,11 +64,6 @@ impl Error { pub(crate) fn new_ws() -> Self { Self::new(Kind::Ws) } - - pub(crate) fn with_cause(mut self, cause: impl Into>) -> Self { - self.inner.cause = Some(cause.into()); - self - } } impl From for Response { @@ -78,12 +73,12 @@ impl From for Response { _ => StatusCode::INTERNAL_SERVER_ERROR, }; - Response::new(status_code).set_body(Body::from(err.to_string())) + Response::new(status_code).set_body(AnyBody::from(err.to_string())) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] -pub enum Kind { +pub(crate) enum Kind { #[display(fmt = "error processing HTTP")] Http, diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 63fa02e7b..431e9fb3e 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -195,6 +195,7 @@ mod tests { use super::*; // copy of encoding from actix-web headers + #[allow(clippy::enum_variant_names)] // allow Encoding prefix on EncodingExt #[derive(Clone, PartialEq, Debug)] pub enum Encoding { Chunked, diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index e934f94dc..c5fcb625c 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -357,7 +357,7 @@ impl fmt::Debug for ResponseBuilder { #[cfg(test)] mod tests { use super::*; - use crate::body::Body; + use crate::body::AnyBody; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] @@ -390,13 +390,13 @@ mod tests { fn test_content_type() { let resp = Response::build(StatusCode::OK) .content_type("text/plain") - .body(Body::empty()); + .body(AnyBody::empty()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } #[test] fn test_into_builder() { - let mut resp: Response = "test".into(); + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); resp.headers_mut().insert( diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 0eaaabcc7..e7dd78171 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -5,7 +5,7 @@ extern crate tls_openssl as openssl; use std::{convert::Infallible, io}; use actix_http::{ - body::{AnyBody, Body, SizedStream}, + body::{AnyBody, SizedStream}, error::PayloadError, http::{ header::{self, HeaderValue}, @@ -409,7 +409,7 @@ impl From for Response { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, _>(BadRequest)) + .h2(|_| err::, _>(BadRequest)) .openssl(tls_config()) .map_err(|_| ()) }) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index a9f6e99f8..320c9ad92 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -10,7 +10,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, Body, SizedStream}, + body::{AnyBody, SizedStream}, error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, @@ -477,7 +477,7 @@ impl From for Response { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, _>(BadRequest)) + .h2(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; @@ -494,7 +494,7 @@ async fn test_h2_service_error() { async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() - .h1(|_| err::, _>(BadRequest)) + .h1(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index ea78ce113..2dca09e21 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -6,7 +6,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, Body, SizedStream}, + body::{AnyBody, SizedStream}, header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode, }; @@ -724,7 +724,7 @@ impl From for Response { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| err::, _>(BadRequest)) + .h1(|_| err::, _>(BadRequest)) .tcp() }) .await; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index f27a8c368..05f2a6495 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -8,7 +8,7 @@ use std::{ use actix_codec::Framed; use actix_http::{ - body::Body, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, + body::AnyBody, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, }; use actix_service::Service; use futures_core::{future::LocalBoxFuture, ready}; @@ -30,7 +30,7 @@ pub type BoxConnectorService = Rc< pub type BoxedSocket = Box; pub enum ConnectRequest { - Client(RequestHeadType, Body, Option), + Client(RequestHeadType, AnyBody, Option), Tunnel(RequestHead, Option), } diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index cb8c0f1bf..46a00b000 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -5,7 +5,7 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - body::Body, + body::AnyBody, http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, RequestHead, }; @@ -45,7 +45,7 @@ impl FrozenClientRequest { /// Send a body. pub fn send_body(&self, body: B) -> SendClientRequest where - B: Into, + B: Into, { RequestSender::Rc(self.head.clone(), None).send_body( self.addr, @@ -158,7 +158,7 @@ impl FrozenSendBuilder { /// Complete request construction and send a body. pub fn send_body(self, body: B) -> SendClientRequest where - B: Into, + B: Into, { if let Some(e) = self.err { return e.into(); diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index f01136d14..12a71f7cb 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::Body, + body::AnyBody, http::{header, Method, StatusCode, Uri}, RequestHead, RequestHeadType, }; @@ -95,7 +95,7 @@ where }; let body_opt = match body { - Body::Bytes(ref b) => Some(b.clone()), + AnyBody::Bytes(ref b) => Some(b.clone()), _ => None, }; @@ -192,14 +192,14 @@ where let body_new = if is_redirect { // try to reuse body match body { - Some(ref bytes) => Body::Bytes(bytes.clone()), - // TODO: should this be Body::Empty or Body::None. - _ => Body::empty(), + Some(ref bytes) => AnyBody::Bytes(bytes.clone()), + // TODO: should this be AnyBody::Empty or AnyBody::None. + _ => AnyBody::empty(), } } else { body = None; // remove body - Body::None + AnyBody::None }; let mut headers = headers.take().unwrap(); diff --git a/awc/src/request.rs b/awc/src/request.rs index 812c76318..bc3859e2e 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -5,7 +5,7 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - body::Body, + body::AnyBody, http::{ header::{self, IntoHeaderPair}, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, @@ -350,7 +350,7 @@ impl ClientRequest { /// Complete request construction and send body. pub fn send_body(self, body: B) -> SendClientRequest where - B: Into, + B: Into, { let slf = match self.prep_for_sending() { Ok(slf) => slf, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 02870aea9..7e1bcd646 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -9,7 +9,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, Body, BodyStream}, + body::{AnyBody, BodyStream}, http::{ header::{self, HeaderMap, HeaderName, IntoHeaderValue}, Error as HttpError, @@ -196,7 +196,7 @@ impl RequestSender { body: B, ) -> SendClientRequest where - B: Into, + B: Into, { let req = match self { RequestSender::Owned(head) => { @@ -236,7 +236,7 @@ impl RequestSender { response_decompress, timeout, config, - Body::Bytes(Bytes::from(body)), + AnyBody::Bytes(Bytes::from(body)), ) } @@ -265,7 +265,7 @@ impl RequestSender { response_decompress, timeout, config, - Body::Bytes(Bytes::from(body)), + AnyBody::Bytes(Bytes::from(body)), ) } @@ -297,7 +297,7 @@ impl RequestSender { timeout: Option, config: &ClientConfig, ) -> SendClientRequest { - self.send_body(addr, response_decompress, timeout, config, Body::empty()) + self.send_body(addr, response_decompress, timeout, config, AnyBody::empty()) } fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> diff --git a/src/app.rs b/src/app.rs index da5b45f3a..a291a959e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use std::future::Future; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::body::{Body, MessageBody}; +use actix_http::body::{AnyBody, MessageBody}; use actix_http::{Extensions, Request}; use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ @@ -39,7 +39,7 @@ pub struct App { _phantom: PhantomData, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { diff --git a/src/dev.rs b/src/dev.rs index 27c206e70..59805b822 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -14,6 +14,7 @@ pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; +#[allow(deprecated)] pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream}; #[cfg(feature = "__compress")] diff --git a/src/error/internal.rs b/src/error/internal.rs index 1d9ca904e..3d99012dc 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt, io::Write as _}; -use actix_http::{body::Body, header, StatusCode}; +use actix_http::{body::AnyBody, header, StatusCode}; use bytes::{BufMut as _, BytesMut}; use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError}; @@ -88,7 +88,7 @@ where header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain; charset=utf-8"), ); - res.set_body(Body::from(buf.into_inner())) + res.set_body(AnyBody::from(buf.into_inner())) } InternalErrorType::Response(ref resp) => { diff --git a/src/responder.rs b/src/responder.rs index 4d2e97c36..8a84be598 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use actix_http::{ - body::Body, + body::AnyBody, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; use bytes::{Bytes, BytesMut}; @@ -65,7 +65,7 @@ impl Responder for HttpResponse { } } -impl Responder for actix_http::Response { +impl Responder for actix_http::Response { #[inline] fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from(self) @@ -254,7 +254,7 @@ pub(crate) mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { - Body::Bytes(ref b) => { + AnyBody::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"some")); } @@ -267,14 +267,14 @@ pub(crate) mod tests { fn body(&self) -> &AnyBody; } - impl BodyTest for Body { + impl BodyTest for AnyBody { fn bin_ref(&self) -> &[u8] { match self { AnyBody::Bytes(ref bin) => bin, _ => unreachable!("bug in test impl"), } } - fn body(&self) -> &Body { + fn body(&self) -> &AnyBody { self } } diff --git a/src/response/builder.rs b/src/response/builder.rs index e42d85f59..e61f7e16f 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -436,7 +436,7 @@ mod tests { use super::*; use crate::{ - dev::Body, + dev::AnyBody, http::{ header::{self, HeaderValue, CONTENT_TYPE}, StatusCode, @@ -475,7 +475,7 @@ mod tests { fn test_content_type() { let resp = HttpResponseBuilder::new(StatusCode::OK) .content_type("text/plain") - .body(Body::empty()); + .body(AnyBody::empty()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } diff --git a/src/response/response.rs b/src/response/response.rs index 46360e536..6475a3816 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, Body, MessageBody}, + body::{AnyBody, MessageBody}, http::{header::HeaderMap, StatusCode}, Extensions, Response, ResponseHead, }; @@ -273,14 +273,14 @@ impl From> for Response { } } -// Future is only implemented for Body payload type because it's the most useful for making simple -// handlers without async blocks. Making it generic over all MessageBody types requires a future -// impl on Response which would cause it's body field to be, undesirably, Option. +// Future is only implemented for AnyBody payload type because it's the most useful for making +// simple handlers without async blocks. Making it generic over all MessageBody types requires a +// future impl on Response which would cause it's body field to be, undesirably, Option. // // This impl is not particularly efficient due to the Response construction and should probably // not be invoked if performance is important. Prefer an async fn/block in such cases. -impl Future for HttpResponse { - type Output = Result, Error>; +impl Future for HttpResponse { + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if let Some(err) = self.error.take() { diff --git a/src/scope.rs b/src/scope.rs index 7d914f581..c20b5d7c8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -580,7 +580,7 @@ mod tests { use bytes::Bytes; use crate::{ - dev::Body, + dev::AnyBody, guard, http::{header, HeaderValue, Method, StatusCode}, middleware::DefaultHeaders, @@ -752,7 +752,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { - Body::Bytes(ref b) => { + AnyBody::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); } @@ -853,7 +853,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { - Body::Bytes(ref b) => { + AnyBody::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } @@ -881,7 +881,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { - Body::Bytes(ref b) => { + AnyBody::Bytes(ref b) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); } diff --git a/src/test.rs b/src/test.rs index 43bf612c6..77765e267 100644 --- a/src/test.rs +++ b/src/test.rs @@ -22,7 +22,7 @@ use crate::{ app_service::AppInitServiceState, config::AppConfig, data::Data, - dev::{Body, MessageBody, Payload}, + dev::{AnyBody, MessageBody, Payload}, http::header::ContentType, rmap::ResourceMap, service::{ServiceRequest, ServiceResponse}, @@ -32,14 +32,14 @@ use crate::{ /// Create service that always responds with `HttpResponse::Ok()` and no body. pub fn ok_service( -) -> impl Service, Error = Error> { +) -> impl Service, Error = Error> { default_service(StatusCode::OK) } /// Create service that always responds with given status code and no body. pub fn default_service( status_code: StatusCode, -) -> impl Service, Error = Error> { +) -> impl Service, Error = Error> { (move |req: ServiceRequest| { ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) }) diff --git a/tests/test_server.rs b/tests/test_server.rs index d21dac8cf..3f0fbfccc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -200,7 +200,7 @@ async fn test_body_encoding_override() { .body(STR) }))) .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); + let body = actix_web::dev::AnyBody::Bytes(STR.into()); let mut response = HttpResponse::with_body(actix_web::http::StatusCode::OK, body); From 0a135c7dc912191b11ed3350bf18ce82b6045483 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 22:41:24 +0000 Subject: [PATCH 098/861] bump actix-codec to 0.4.1 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ca34c924..9a00db7b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] __compress = [] [dependencies] -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-macros = "0.2.3" actix-rt = "2.2" actix-server = "2.0.0-beta.9" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index b111f8685..f118d1627 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-tls = "3.0.0-beta.7" actix-utils = "3.0.0" actix-rt = "2.2" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 27a147379..a2e5e284d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -43,7 +43,7 @@ __compress = [] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-utils = "3.0.0" actix-rt = "2.2" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 58c0d31a5..bc660293d 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -28,7 +28,7 @@ rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"] openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-http = "3.0.0-beta.12" actix-http-test = "3.0.0-beta.6" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 706a90c00..c20e508a4 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-http = "3.0.0-beta.12" actix-web = { version = "4.0.0-beta.11", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ce710d58d..7bc99c08c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -53,7 +53,7 @@ trust-dns = ["trust-dns-resolver"] __compress = [] [dependencies] -actix-codec = "0.4.0" +actix-codec = "0.4.1" actix-service = "2.0.0" actix-http = "3.0.0-beta.12" actix-rt = { version = "2.1", default-features = false } From 84c6d25fd32ea06febf8a84d148ab234fc835efb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 23:07:08 +0000 Subject: [PATCH 099/861] bump env logger dep --- Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a00db7b3..23a5fc8ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ awc = { version = "3.0.0-beta.10", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } -env_logger = "0.8" +env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } rand = "0.8" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a2e5e284d..682104cd1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -86,7 +86,7 @@ actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } -env_logger = "0.8" +env_logger = "0.9" rcgen = "0.8" regex = "1.3" rustls-pemfile = "0.2" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c20e508a4..c938c6a1d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -30,5 +30,5 @@ actix-rt = "2.2" actix-test = "0.1.0-beta.6" awc = { version = "3.0.0-beta.10", default-features = false } -env_logger = "0.8" +env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7bc99c08c..048fe78d7 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -97,7 +97,7 @@ actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } brotli2 = "0.3.2" -env_logger = "0.8" +env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" From 68a3acb9c290945eab82feeb87a98c47abcf0fac Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Nov 2021 23:22:29 +0000 Subject: [PATCH 100/861] bump zstd dep --- Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23a5fc8ca..3f1f54fcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ rcgen = "0.8" rustls-pemfile = "0.2" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -zstd = "0.7" +zstd = "0.9" [profile.dev] # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 682104cd1..c2de71e10 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -78,7 +78,7 @@ actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = tru # compression brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.7", optional = true } +zstd = { version = "0.9", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.9" From 168a7284d300b212f53fb40183d860792cabd5f1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Nov 2021 21:13:05 +0800 Subject: [PATCH 101/861] fix actix_http::Error conversion. (#2449) --- actix-http/src/body/body.rs | 46 ++++++++++++++++++------------------- actix-http/src/body/mod.rs | 45 +++++++++++++++++++----------------- actix-http/src/error.rs | 2 +- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 1d88777bc..04fc957c7 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -139,56 +139,56 @@ impl fmt::Debug for AnyBody { } } -impl From<&'static str> for AnyBody { - fn from(string: &'static str) -> AnyBody { - AnyBody::Bytes(Bytes::from_static(string.as_ref())) +impl From<&'static str> for AnyBody { + fn from(string: &'static str) -> Self { + Self::Bytes(Bytes::from_static(string.as_ref())) } } -impl From<&'static [u8]> for AnyBody { - fn from(bytes: &'static [u8]) -> AnyBody { - AnyBody::Bytes(Bytes::from_static(bytes)) +impl From<&'static [u8]> for AnyBody { + fn from(bytes: &'static [u8]) -> Self { + Self::Bytes(Bytes::from_static(bytes)) } } -impl From> for AnyBody { - fn from(vec: Vec) -> AnyBody { - AnyBody::Bytes(Bytes::from(vec)) +impl From> for AnyBody { + fn from(vec: Vec) -> Self { + Self::Bytes(Bytes::from(vec)) } } -impl From for AnyBody { - fn from(string: String) -> AnyBody { - string.into_bytes().into() +impl From for AnyBody { + fn from(string: String) -> Self { + Self::Bytes(Bytes::from(string)) } } -impl From<&'_ String> for AnyBody { - fn from(string: &String) -> AnyBody { - AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string))) +impl From<&'_ String> for AnyBody { + fn from(string: &String) -> Self { + Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string))) } } -impl From> for AnyBody { - fn from(string: Cow<'_, str>) -> AnyBody { +impl From> for AnyBody { + fn from(string: Cow<'_, str>) -> Self { match string { - Cow::Owned(s) => AnyBody::from(s), + Cow::Owned(s) => Self::from(s), Cow::Borrowed(s) => { - AnyBody::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) + Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) } } } } -impl From for AnyBody { +impl From for AnyBody { fn from(bytes: Bytes) -> Self { - AnyBody::Bytes(bytes) + Self::Bytes(bytes) } } -impl From for AnyBody { +impl From for AnyBody { fn from(bytes: BytesMut) -> Self { - AnyBody::Bytes(bytes.freeze()) + Self::Bytes(bytes.freeze()) } } diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 83299a471..724e20597 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -86,11 +86,14 @@ mod tests { } } + /// AnyBody alias because rustc does not (can not?) infer the default type parameter. + type TestBody = AnyBody; + #[actix_rt::test] async fn test_static_str() { - assert_eq!(AnyBody::from("").size(), BodySize::Sized(0)); - assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from("test").get_ref(), b"test"); + assert_eq!(TestBody::from("").size(), BodySize::Sized(0)); + assert_eq!(TestBody::from("test").size(), BodySize::Sized(4)); + assert_eq!(TestBody::from("test").get_ref(), b"test"); assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!( @@ -104,14 +107,14 @@ mod tests { #[actix_rt::test] async fn test_static_bytes() { - assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!(TestBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( - AnyBody::copy_from_slice(b"test".as_ref()).size(), + TestBody::copy_from_slice(b"test".as_ref()).size(), BodySize::Sized(4) ); assert_eq!( - AnyBody::copy_from_slice(b"test".as_ref()).get_ref(), + TestBody::copy_from_slice(b"test".as_ref()).get_ref(), b"test" ); let sb = Bytes::from(&b"test"[..]); @@ -126,8 +129,8 @@ mod tests { #[actix_rt::test] async fn test_vec() { - assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test"); + assert_eq!(TestBody::from(Vec::from("test")).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(Vec::from("test")).get_ref(), b"test"); let test_vec = Vec::from("test"); pin!(test_vec); @@ -144,8 +147,8 @@ mod tests { #[actix_rt::test] async fn test_bytes() { let b = Bytes::from("test"); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -158,8 +161,8 @@ mod tests { #[actix_rt::test] async fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -172,10 +175,10 @@ mod tests { #[actix_rt::test] async fn test_string() { let b = "test".to_owned(); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); - assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(&b).get_ref(), b"test"); + assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(TestBody::from(&b).size(), BodySize::Sized(4)); + assert_eq!(TestBody::from(&b).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -216,22 +219,22 @@ mod tests { #[actix_rt::test] async fn test_body_debug() { - assert!(format!("{:?}", AnyBody::::None).contains("Body::None")); - assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1')); + assert!(format!("{:?}", TestBody::None).contains("Body::None")); + assert!(format!("{:?}", TestBody::from(Bytes::from_static(b"1"))).contains('1')); } #[actix_rt::test] async fn test_serde_json() { use serde_json::{json, Value}; assert_eq!( - AnyBody::from( + TestBody::from( serde_json::to_vec(&Value::String("test".to_owned())).unwrap() ) .size(), BodySize::Sized(6) ); assert_eq!( - AnyBody::from( + TestBody::from( serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap() ) .size(), diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index c7c0cce0e..970c0c564 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -66,7 +66,7 @@ impl Error { } } -impl From for Response { +impl From for Response> { fn from(err: Error) -> Self { let status_code = match err.inner.kind { Kind::Parse => StatusCode::BAD_REQUEST, From 1fe309bcc6b0a472f1d92b3dc51e36d2d1f9db4a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 17 Nov 2021 15:32:42 +0000 Subject: [PATCH 102/861] increase ci test timeout --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8b21b7fb..8f586d8d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: - name: tests uses: actions-rs/cargo@v1 - timeout-minutes: 40 + timeout-minutes: 60 with: command: ci-test args: --skip=test_reading_deflate_encoding_large_random_rustls @@ -174,5 +174,5 @@ jobs: - name: doc tests uses: actions-rs/cargo@v1 - timeout-minutes: 40 + timeout-minutes: 60 with: { command: ci-doctest } From e33618ed6d222ffbf7d095adaa53500e024204ea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 17 Nov 2021 17:43:24 +0000 Subject: [PATCH 103/861] ensure content disposition header in multipart (#2451) Co-authored-by: Craig Pastro --- actix-multipart/CHANGES.md | 8 + actix-multipart/src/error.rs | 23 ++- actix-multipart/src/server.rs | 228 ++++++++++++++++++------- src/http/header/content_disposition.rs | 24 ++- 4 files changed, 213 insertions(+), 70 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 09cc707be..97c011393 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +* Added `MultipartError::NoContentDisposition` variant. [#2451] +* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +* Added `Field::name` method for getting the field name. [#2451] +* `MultipartError` now marks variants with inner errors as the source. [#2451] +* `MultipartError` is now marked as non-exhaustive. [#2451] + +[#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index 5f91c60df..de4594b8f 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -2,39 +2,52 @@ use actix_web::error::{ParseError, PayloadError}; use actix_web::http::StatusCode; use actix_web::ResponseError; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; /// A set of errors that can occur during parsing multipart streams -#[derive(Debug, Display, From)] +#[non_exhaustive] +#[derive(Debug, Display, From, Error)] pub enum MultipartError { + /// Content-Disposition header is not found or is not equal to "form-data". + /// + /// According to [RFC 7578](https://tools.ietf.org/html/rfc7578#section-4.2) a + /// Content-Disposition header must always be present and equal to "form-data". + #[display(fmt = "No Content-Disposition `form-data` header")] + NoContentDisposition, + /// Content-Type header is not found - #[display(fmt = "No Content-type header found")] + #[display(fmt = "No Content-Type header found")] NoContentType, + /// Can not parse Content-Type header #[display(fmt = "Can not parse Content-Type header")] ParseContentType, + /// Multipart boundary is not found #[display(fmt = "Multipart boundary is not found")] Boundary, + /// Nested multipart is not supported #[display(fmt = "Nested multipart is not supported")] Nested, + /// Multipart stream is incomplete #[display(fmt = "Multipart stream is incomplete")] Incomplete, + /// Error during field parsing #[display(fmt = "{}", _0)] Parse(ParseError), + /// Payload error #[display(fmt = "{}", _0)] Payload(PayloadError), + /// Not consumed #[display(fmt = "Multipart stream is not consumed")] NotConsumed, } -impl std::error::Error for MultipartError {} - /// Return `BadRequest` for `MultipartError` impl ResponseError for MultipartError { fn status_code(&self) -> StatusCode { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index b7d251537..43f9ccf5f 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,15 +1,20 @@ //! Multipart response payload support. -use std::cell::{Cell, RefCell, RefMut}; -use std::convert::TryFrom; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{cmp, fmt}; +use std::{ + cell::{Cell, RefCell, RefMut}, + cmp, + convert::TryFrom, + fmt, + marker::PhantomData, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; -use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; +use actix_web::{ + error::{ParseError, PayloadError}, + http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}, +}; use bytes::{Bytes, BytesMut}; use futures_core::stream::{LocalBoxStream, Stream}; use futures_util::stream::StreamExt as _; @@ -40,10 +45,13 @@ enum InnerMultipartItem { enum InnerState { /// Stream eof Eof, + /// Skip data until first boundary FirstBoundary, + /// Reading boundary Boundary, + /// Reading Headers, Headers, } @@ -332,31 +340,55 @@ impl InnerMultipart { return Poll::Pending; }; - // content type - let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - mt = ct; - } - } - } + // According to [RFC 7578](https://tools.ietf.org/html/rfc7578#section-4.2) a + // Content-Disposition header must always be present and set to "form-data". + + let content_disposition = headers + .get(&header::CONTENT_DISPOSITION) + .and_then(|cd| ContentDisposition::from_raw(cd).ok()) + .filter(|content_disposition| { + let is_form_data = + content_disposition.disposition == header::DispositionType::FormData; + + let has_field_name = content_disposition + .parameters + .iter() + .any(|param| matches!(param, header::DispositionParam::Name(_))); + + is_form_data && has_field_name + }); + + let cd = if let Some(content_disposition) = content_disposition { + content_disposition + } else { + return Poll::Ready(Some(Err(MultipartError::NoContentDisposition))); + }; + + let ct: mime::Mime = headers + .get(&header::CONTENT_TYPE) + .and_then(|ct| ct.to_str().ok()) + .and_then(|ct| ct.parse().ok()) + .unwrap_or(mime::APPLICATION_OCTET_STREAM); self.state = InnerState::Boundary; - // nested multipart stream - if mt.type_() == mime::MULTIPART { - Poll::Ready(Some(Err(MultipartError::Nested))) - } else { - let field = Rc::new(RefCell::new(InnerField::new( - self.payload.clone(), - self.boundary.clone(), - &headers, - )?)); - self.item = InnerMultipartItem::Field(Rc::clone(&field)); - - Poll::Ready(Some(Ok(Field::new(safety.clone(cx), headers, mt, field)))) + // nested multipart stream is not supported + if ct.type_() == mime::MULTIPART { + return Poll::Ready(Some(Err(MultipartError::Nested))); } + + let field = + InnerField::new_in_rc(self.payload.clone(), self.boundary.clone(), &headers)?; + + self.item = InnerMultipartItem::Field(Rc::clone(&field)); + + Poll::Ready(Some(Ok(Field::new( + safety.clone(cx), + headers, + ct, + cd, + field, + )))) } } } @@ -371,6 +403,7 @@ impl Drop for InnerMultipart { /// A single field in a multipart stream pub struct Field { ct: mime::Mime, + cd: ContentDisposition, headers: HeaderMap, inner: Rc>, safety: Safety, @@ -381,35 +414,51 @@ impl Field { safety: Safety, headers: HeaderMap, ct: mime::Mime, + cd: ContentDisposition, inner: Rc>, ) -> Self { Field { ct, + cd, headers, inner, safety, } } - /// Get a map of headers + /// Returns a reference to the field's header map. pub fn headers(&self) -> &HeaderMap { &self.headers } - /// Get the content type of the field + /// Returns a reference to the field's content (mime) type. pub fn content_type(&self) -> &mime::Mime { &self.ct } - /// Get the content disposition of the field, if it exists - pub fn content_disposition(&self) -> Option { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { - ContentDisposition::from_raw(content_disposition).ok() - } else { - None - } + /// Returns the field's Content-Disposition. + /// + /// Per [RFC 7578 §4.2]: 'Each part MUST contain a Content-Disposition header field where the + /// disposition type is "form-data". The Content-Disposition header field MUST also contain an + /// additional parameter of "name"; the value of the "name" parameter is the original field name + /// from the form.' + /// + /// This crate validates that it exists before returning a `Field`. As such, it is safe to + /// unwrap `.content_disposition().get_name()`. The [name](Self::name) method is provided as + /// a convenience. + /// + /// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2 + pub fn content_disposition(&self) -> &ContentDisposition { + &self.cd + } + + /// Returns the field's name. + /// + /// See [content_disposition] regarding guarantees about + pub fn name(&self) -> &str { + self.content_disposition() + .get_name() + .expect("field name should be guaranteed to exist in multipart form-data") } } @@ -451,20 +500,23 @@ struct InnerField { } impl InnerField { + fn new_in_rc( + payload: PayloadRef, + boundary: String, + headers: &HeaderMap, + ) -> Result>, PayloadError> { + Self::new(payload, boundary, headers).map(|this| Rc::new(RefCell::new(this))) + } + fn new( payload: PayloadRef, boundary: String, headers: &HeaderMap, ) -> Result { let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some(len) - } else { - return Err(PayloadError::Incomplete(None)); - } - } else { - return Err(PayloadError::Incomplete(None)); + match len.to_str().ok().and_then(|len| len.parse::().ok()) { + Some(len) => Some(len), + None => return Err(PayloadError::Incomplete(None)), } } else { None @@ -658,9 +710,8 @@ impl Clone for PayloadRef { } } -/// Counter. It tracks of number of clones of payloads and give access to -/// payload only to top most task panics if Safety get destroyed and it not top -/// most task. +/// Counter. It tracks of number of clones of payloads and give access to payload only to top most +/// task panics if Safety get destroyed and it not top most task. #[derive(Debug)] struct Safety { task: LocalWaker, @@ -707,11 +758,12 @@ impl Drop for Safety { if Rc::strong_count(&self.payload) != self.level { self.clean.set(true); } + self.task.wake(); } } -/// Payload buffer +/// Payload buffer. struct PayloadBuffer { eof: bool, buf: BytesMut, @@ -719,7 +771,7 @@ struct PayloadBuffer { } impl PayloadBuffer { - /// Create new `PayloadBuffer` instance + /// Constructs new `PayloadBuffer` instance. fn new(stream: S) -> Self where S: Stream> + 'static, @@ -767,7 +819,7 @@ impl PayloadBuffer { } /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Result, MultipartError> { + fn read_until(&mut self, line: &[u8]) -> Result, MultipartError> { let res = twoway::find_bytes(&self.buf, line) .map(|idx| self.buf.split_to(idx + line.len()).freeze()); @@ -779,12 +831,12 @@ impl PayloadBuffer { } /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Result, MultipartError> { + fn readline(&mut self) -> Result, MultipartError> { self.read_until(b"\n") } /// Read bytes until new line delimiter or eof - pub fn readline_or_eof(&mut self) -> Result, MultipartError> { + fn readline_or_eof(&mut self) -> Result, MultipartError> { match self.readline() { Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.split().freeze())), line => line, @@ -792,7 +844,7 @@ impl PayloadBuffer { } /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { + fn unprocessed(&mut self, data: Bytes) { let buf = BytesMut::from(data.as_ref()); let buf = std::mem::replace(&mut self.buf, buf); self.buf.extend_from_slice(&buf); @@ -914,6 +966,7 @@ mod tests { Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ test\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ data\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", @@ -965,7 +1018,7 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await { Some(Ok(mut field)) => { - let cd = field.content_disposition().unwrap(); + let cd = field.content_disposition(); assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); @@ -1027,7 +1080,7 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await.unwrap() { Ok(mut field) => { - let cd = field.content_disposition().unwrap(); + let cd = field.content_disposition(); assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); @@ -1182,4 +1235,59 @@ mod tests { _ => unreachable!(), } } + + #[actix_rt::test] + async fn no_content_disposition() { + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n", + ); + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + let payload = SlowStream::new(bytes); + + let mut multipart = Multipart::new(&headers, payload); + let res = multipart.next().await.unwrap(); + assert!(res.is_err()); + assert!(matches!( + res.unwrap_err(), + MultipartError::NoContentDisposition, + )); + } + + #[actix_rt::test] + async fn no_name_in_content_disposition() { + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n", + ); + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + let payload = SlowStream::new(bytes); + + let mut multipart = Multipart::new(&headers, payload); + let res = multipart.next().await.unwrap(); + assert!(res.is_err()); + assert!(matches!( + res.unwrap_err(), + MultipartError::NoContentDisposition, + )); + } } diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index fdd8a7dac..6d07a41bd 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -34,15 +34,18 @@ fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { /// The implied disposition of the content of the HTTP body. #[derive(Clone, Debug, PartialEq)] pub enum DispositionType { - /// Inline implies default processing + /// Inline implies default processing. Inline, + /// Attachment implies that the recipient should prompt the user to save the response locally, /// rather than process it normally (as per its media type). Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. + + /// Used in *multipart/form-data* as defined in [RFC7578](https://tools.ietf.org/html/rfc7578) + /// to carry the field name and optional filename. FormData, - /// Extension type. Should be handled by recipients the same way as Attachment + + /// Extension type. Should be handled by recipients the same way as Attachment. Ext(String), } @@ -76,6 +79,7 @@ pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from /// the form. Name(String), + /// A plain file name. /// /// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any @@ -83,14 +87,17 @@ pub enum DispositionParam { /// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead /// in case there are Unicode characters in file names. Filename(String), + /// An extended file name. It must not exist for `ContentType::Formdata` according to /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). FilenameExt(ExtendedValue), + /// An unrecognized regular parameter as defined in /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should /// ignore unrecognizable parameters. Unknown(String, String), + /// An unrecognized extended parameter as defined in /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single @@ -205,7 +212,6 @@ impl DispositionParam { /// itself, *Content-Disposition* has no effect. /// /// # ABNF - /// ```text /// content-disposition = "Content-Disposition" ":" /// disposition-type *( ";" disposition-parm ) @@ -289,10 +295,12 @@ impl DispositionParam { /// If "filename" parameter is supplied, do not use the file name blindly, check and possibly /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3). +// TODO: private fields and use smallvec #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { /// The disposition type pub disposition: DispositionType, + /// Disposition parameters pub parameters: Vec, } @@ -509,22 +517,28 @@ impl fmt::Display for DispositionParam { // // // See also comments in test_from_raw_unnecessary_percent_decode. + static RE: Lazy = Lazy::new(|| Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap()); + match self { DispositionParam::Name(ref value) => write!(f, "name={}", value), + DispositionParam::Filename(ref value) => { write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) } + DispositionParam::Unknown(ref name, ref value) => write!( f, "{}=\"{}\"", name, &RE.replace_all(value, "\\$0").as_ref() ), + DispositionParam::FilenameExt(ref ext_value) => { write!(f, "filename*={}", ext_value) } + DispositionParam::UnknownExt(ref name, ref ext_value) => { write!(f, "{}*={}", name, ext_value) } From 66620a101211c424ffc7e2f1b1c7bb9e2a78ff87 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 17 Nov 2021 23:11:35 +0300 Subject: [PATCH 104/861] simplify handler.rs (#2450) --- src/handler.rs | 152 ++++++------------------------------------------- src/route.rs | 6 +- 2 files changed, 21 insertions(+), 137 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index bc91ce41b..ddefe8d53 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,16 +1,13 @@ use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ready, Ready}; -use futures_core::ready; -use pin_project::pin_project; +use actix_service::{ + boxed::{self, BoxServiceFactory}, + fn_service, +}; use crate::{ service::{ServiceRequest, ServiceResponse}, - Error, FromRequest, HttpRequest, HttpResponse, Responder, + Error, FromRequest, HttpResponse, Responder, }; /// A request handler is an async function that accepts zero or more parameters that can be @@ -27,139 +24,26 @@ where fn call(&self, param: T) -> R; } -#[doc(hidden)] -/// Extract arguments from request, run factory function and make response. -pub struct HandlerService +pub fn handler_service( + handler: F, +) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()> where F: Handler, T: FromRequest, R: Future, R::Output: Responder, { - hnd: F, - _phantom: PhantomData<(T, R)>, -} - -impl HandlerService -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - pub fn new(hnd: F) -> Self { - Self { - hnd, - _phantom: PhantomData, + boxed::factory(fn_service(move |req: ServiceRequest| { + let handler = handler.clone(); + async move { + let (req, mut payload) = req.into_parts(); + let res = match T::from_request(&req, &mut payload).await { + Err(err) => HttpResponse::from_error(err), + Ok(data) => handler.call(data).await.respond_to(&req), + }; + Ok(ServiceResponse::new(req, res)) } - } -} - -impl Clone for HandlerService -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - fn clone(&self) -> Self { - Self { - hnd: self.hnd.clone(), - _phantom: PhantomData, - } - } -} - -impl ServiceFactory for HandlerService -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - type Response = ServiceResponse; - type Error = Error; - type Config = (); - type Service = Self; - type InitError = (); - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ready(Ok(self.clone())) - } -} - -/// HandlerService is both it's ServiceFactory and Service Type. -impl Service for HandlerService -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - type Response = ServiceResponse; - type Error = Error; - type Future = HandlerServiceFuture; - - actix_service::always_ready!(); - - 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()) - } -} - -#[doc(hidden)] -#[pin_project(project = HandlerProj)] -pub enum HandlerServiceFuture -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - Extract(#[pin] T::Future, Option, F), - Handle(#[pin] R, Option), -} - -impl Future for HandlerServiceFuture -where - F: Handler, - T: FromRequest, - R: Future, - R::Output: Responder, -{ - // Error type in this future is a placeholder type. - // all instances of error must be converted to ServiceResponse and return in Ok. - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match self.as_mut().project() { - HandlerProj::Extract(fut, req, handle) => { - match ready!(fut.poll(cx)) { - Ok(item) => { - let fut = handle.call(item); - let state = HandlerServiceFuture::Handle(fut, req.take()); - self.as_mut().set(state); - } - Err(err) => { - let req = req.take().unwrap(); - let res = HttpResponse::from_error(err.into()); - return Poll::Ready(Ok(ServiceResponse::new(req, res))); - } - }; - } - HandlerProj::Handle(fut, req) => { - let res = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - let res = res.respond_to(&req); - return Poll::Ready(Ok(ServiceResponse::new(req, res))); - } - } - } - } + })) } /// FromRequest trait impl for tuples diff --git a/src/route.rs b/src/route.rs index d85b940bd..0c0699430 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use futures_core::future::LocalBoxFuture; use crate::{ guard::{self, Guard}, - handler::{Handler, HandlerService}, + handler::{handler_service, Handler}, service::{ServiceRequest, ServiceResponse}, Error, FromRequest, HttpResponse, Responder, }; @@ -30,7 +30,7 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: boxed::factory(HandlerService::new(HttpResponse::NotFound)), + service: handler_service(HttpResponse::NotFound), guards: Rc::new(Vec::new()), } } @@ -182,7 +182,7 @@ impl Route { R: Future + 'static, R::Output: Responder + 'static, { - self.service = boxed::factory(HandlerService::new(handler)); + self.service = handler_service(handler); self } From 56ee97f722292cfc7c9dabd6c9039b1d9cee218f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 18 Nov 2021 18:14:34 +0000 Subject: [PATCH 105/861] add files path traversal tests --- actix-files/src/path_buf.rs | 24 +++++++++++++++++++++++- actix-files/tests/traversal.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 actix-files/tests/traversal.rs diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 76f589307..0e0d4f51d 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -8,7 +8,7 @@ use actix_web::{dev::Payload, FromRequest, HttpRequest}; use crate::error::UriSegmentError; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct PathBufWrap(PathBuf); impl FromStr for PathBufWrap { @@ -21,6 +21,8 @@ impl FromStr for PathBufWrap { impl PathBufWrap { /// Parse a path, giving the choice of allowing hidden files to be considered valid segments. + /// + /// Path traversal is guarded by this method. pub fn parse_path(path: &str, hidden_files: bool) -> Result { let mut buf = PathBuf::new(); @@ -115,4 +117,24 @@ mod tests { PathBuf::from_iter(vec!["test", ".tt"]) ); } + + #[test] + fn path_traversal() { + assert_eq!( + PathBufWrap::parse_path("/../README.md", false).unwrap().0, + PathBuf::from_iter(vec!["README.md"]) + ); + + assert_eq!( + PathBufWrap::parse_path("/../README.md", true).unwrap().0, + PathBuf::from_iter(vec!["README.md"]) + ); + + assert_eq!( + PathBufWrap::parse_path("/../../../../../../../../../../etc/passwd", false) + .unwrap() + .0, + PathBuf::from_iter(vec!["etc/passwd"]) + ); + } } diff --git a/actix-files/tests/traversal.rs b/actix-files/tests/traversal.rs new file mode 100644 index 000000000..c890b3fe4 --- /dev/null +++ b/actix-files/tests/traversal.rs @@ -0,0 +1,27 @@ +use actix_files::Files; +use actix_web::{ + http::StatusCode, + test::{self, TestRequest}, + App, +}; + +#[actix_rt::test] +async fn test_directory_traversal_prevention() { + let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; + + let req = + TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri( + "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd", + ) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/%00/etc/passwd%00").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); +} From 194a69153751e5d28df78ec69444daa80e93f84c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 19 Nov 2021 14:04:12 +0000 Subject: [PATCH 106/861] files: 304 Not Modified responses omit Content-Length header (#2453) --- actix-files/CHANGES.md | 3 ++ actix-files/src/named.rs | 25 ++++++---- actix-http-test/src/lib.rs | 6 +-- actix-http/src/body/body.rs | 2 + actix-http/src/config.rs | 2 + actix-http/src/h1/client.rs | 2 +- actix-http/src/h1/codec.rs | 24 ++++----- actix-http/src/h1/encoder.rs | 21 ++++++-- actix-http/src/message.rs | 2 +- actix-http/tests/test_server.rs | 87 +++++++++++++++++++++++++++++++++ actix-test/src/lib.rs | 6 +-- 11 files changed, 145 insertions(+), 35 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index e1a2c90c5..41336c21c 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] + +[#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 241e78cf0..dac548708 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,17 +1,22 @@ -use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, ready, Ready}; -use actix_web::dev::{AppService, HttpServiceFactory, ResourceDef}; -use std::fs::{File, Metadata}; -use std::io; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + fs::{File, Metadata}, + io, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; +use actix_http::body::AnyBody; +use actix_service::{Service, ServiceFactory}; +use actix_utils::future::{ok, ready, Ready}; use actix_web::{ - dev::{BodyEncoding, ServiceRequest, ServiceResponse, SizedStream}, + dev::{ + AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest, + ServiceResponse, SizedStream, + }, http::{ header::{ self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, @@ -443,7 +448,7 @@ impl NamedFile { if precondition_failed { return resp.status(StatusCode::PRECONDITION_FAILED).finish(); } else if not_modified { - return resp.status(StatusCode::NOT_MODIFIED).finish(); + return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None); } let reader = ChunkedReadFile::new(length, offset, self.file); diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index cda98cea5..699bb2660 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -273,12 +273,12 @@ impl TestServer { self.client.headers() } - /// Gracefully stop HTTP server. + /// Stop HTTP server. /// - /// Waits for spawned `Server` and `System` to shutdown gracefully. + /// Waits for spawned `Server` and `System` to (force) shutdown. pub async fn stop(&mut self) { // signal server to stop - self.server.stop(true).await; + self.server.stop(false).await; // also signal system to stop // though this is handled by `ServerBuilder::exit_system` too diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index 04fc957c7..c6439b559 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -32,6 +32,8 @@ pub enum AnyBody { } impl AnyBody { + // TODO: a None body constructor + /// Constructs a new, empty body. pub fn empty() -> Self { Self::Bytes(Bytes::new()) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 069099b8c..5d020edfc 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -20,8 +20,10 @@ pub(crate) const DATE_VALUE_LENGTH: usize = 29; pub enum KeepAlive { /// Keep alive in seconds Timeout(usize), + /// Rely on OS to shutdown tcp connection Os, + /// Disabled Disabled, } diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 4a6104688..bec167971 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -120,7 +120,7 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.ctype() { + if let Some(ctype) = req.conn_type() { // do not use peer's keep-alive self.inner.ctype = if ctype == ConnectionType::KeepAlive { self.inner.ctype diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 634ca25e8..29f6f4170 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -29,7 +29,7 @@ pub struct Codec { decoder: decoder::MessageDecoder, payload: Option, version: Version, - ctype: ConnectionType, + conn_type: ConnectionType, // encoder part flags: Flags, @@ -65,7 +65,7 @@ impl Codec { decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, - ctype: ConnectionType::Close, + conn_type: ConnectionType::Close, encoder: encoder::MessageEncoder::default(), } } @@ -73,13 +73,13 @@ impl Codec { /// Check if request is upgrade. #[inline] pub fn upgrade(&self) -> bool { - self.ctype == ConnectionType::Upgrade + self.conn_type == ConnectionType::Upgrade } /// Check if last response is keep-alive. #[inline] pub fn keepalive(&self) -> bool { - self.ctype == ConnectionType::KeepAlive + self.conn_type == ConnectionType::KeepAlive } /// Check if keep-alive enabled on server level. @@ -124,11 +124,11 @@ impl Decoder for Codec { let head = req.head(); self.flags.set(Flags::HEAD, head.method == Method::HEAD); self.version = head.version; - self.ctype = head.connection_type(); - if self.ctype == ConnectionType::KeepAlive + self.conn_type = head.connection_type(); + if self.conn_type == ConnectionType::KeepAlive && !self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.ctype = ConnectionType::Close + self.conn_type = ConnectionType::Close } match payload { PayloadType::None => self.payload = None, @@ -159,14 +159,14 @@ impl Encoder, BodySize)>> for Codec { res.head_mut().version = self.version; // connection status - self.ctype = if let Some(ct) = res.head().ctype() { + self.conn_type = if let Some(ct) = res.head().conn_type() { if ct == ConnectionType::KeepAlive { - self.ctype + self.conn_type } else { ct } } else { - self.ctype + self.conn_type }; // encode message @@ -177,10 +177,9 @@ impl Encoder, BodySize)>> for Codec { self.flags.contains(Flags::STREAM), self.version, length, - self.ctype, + self.conn_type, &self.config, )?; - // self.headers_size = (dst.len() - len) as u32; } Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; @@ -189,6 +188,7 @@ impl Encoder, BodySize)>> for Codec { self.encoder.encode_eof(dst)?; } } + Ok(()) } } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index e07c32956..cc26a200f 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -56,7 +56,7 @@ pub(crate) trait MessageType: Sized { dst: &mut BytesMut, version: Version, mut length: BodySize, - ctype: ConnectionType, + conn_type: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { let chunked = self.chunked(); @@ -71,14 +71,23 @@ pub(crate) trait MessageType: Sized { | StatusCode::PROCESSING | StatusCode::NO_CONTENT => { // skip content-length and transfer-encoding headers - // See https://tools.ietf.org/html/rfc7230#section-3.3.1 + // see https://tools.ietf.org/html/rfc7230#section-3.3.1 // and https://tools.ietf.org/html/rfc7230#section-3.3.2 skip_len = true; length = BodySize::None } + + StatusCode::NOT_MODIFIED => { + // 304 responses should never have a body but should retain a manually set + // content-length header see https://tools.ietf.org/html/rfc7232#section-4.1 + skip_len = false; + length = BodySize::None; + } + _ => {} } } + match length { BodySize::Stream => { if chunked { @@ -102,7 +111,7 @@ pub(crate) trait MessageType: Sized { } // Connection - match ctype { + match conn_type { ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), ConnectionType::KeepAlive if version < Version::HTTP_11 => { if camel_case { @@ -327,7 +336,7 @@ impl MessageEncoder { stream: bool, version: Version, length: BodySize, - ctype: ConnectionType, + conn_type: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { // transfer encoding @@ -349,7 +358,7 @@ impl MessageEncoder { } message.encode_status(dst)?; - message.encode_headers(dst, version, length, ctype, config) + message.encode_headers(dst, version, length, conn_type, config) } } @@ -363,10 +372,12 @@ pub(crate) struct TransferEncoding { enum TransferEncodingKind { /// An Encoder for when Transfer-Encoding includes `chunked`. Chunked(bool), + /// An Encoder for when Content-Length is set. /// /// Enforces that the body is not longer than the Content-Length header. Length(u64), + /// An Encoder for when Content-Length is not known. /// /// Application decides when to stop writing. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 84125fb3a..e0bed0631 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -317,7 +317,7 @@ impl ResponseHead { } #[inline] - pub(crate) fn ctype(&self) -> Option { + pub(crate) fn conn_type(&self) -> Option { if self.flags.contains(Flags::CLOSE) { Some(ConnectionType::Close) } else if self.flags.contains(Flags::KEEP_ALIVE) { diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 2dca09e21..11bc8e939 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -759,3 +759,90 @@ async fn test_h1_on_connect() { srv.stop().await; } + +/// Tests compliance with 304 Not Modified spec in RFC 7232 §4.1. +/// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 +#[actix_rt::test] +async fn test_not_modified_spec_h1() { + // TODO: this test needing a few seconds to complete reveals some weirdness with either the + // dispatcher or the client, though similar hangs occur on other tests in this file, only + // succeeding, it seems, because of the keepalive timer + + static CL: header::HeaderName = header::CONTENT_LENGTH; + + let mut srv = test_server(|| { + HttpService::build() + .h1(|req: Request| { + let res: Response = match req.path() { + // with no content-length + "/none" => { + Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None) + } + + // with no content-length + "/body" => Response::with_body( + StatusCode::NOT_MODIFIED, + AnyBody::from("1234"), + ), + + // with manual content-length header and specific None body + "/cl-none" => { + let mut res = + Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None); + res.headers_mut() + .insert(CL.clone(), header::HeaderValue::from_static("24")); + res + } + + // with manual content-length header and ignore-able body + "/cl-body" => { + let mut res = Response::with_body( + StatusCode::NOT_MODIFIED, + AnyBody::from("1234"), + ); + res.headers_mut() + .insert(CL.clone(), header::HeaderValue::from_static("4")); + res + } + + _ => panic!("unknown route"), + }; + + ok::<_, Infallible>(res) + }) + .tcp() + }) + .await; + + let res = srv.get("/none").send().await.unwrap(); + assert_eq!(res.status(), http::StatusCode::NOT_MODIFIED); + assert_eq!(res.headers().get(&CL), None); + assert!(srv.load_body(res).await.unwrap().is_empty()); + + let res = srv.get("/body").send().await.unwrap(); + assert_eq!(res.status(), http::StatusCode::NOT_MODIFIED); + assert_eq!(res.headers().get(&CL), None); + assert!(srv.load_body(res).await.unwrap().is_empty()); + + let res = srv.get("/cl-none").send().await.unwrap(); + assert_eq!(res.status(), http::StatusCode::NOT_MODIFIED); + assert_eq!( + res.headers().get(&CL), + Some(&header::HeaderValue::from_static("24")), + ); + assert!(srv.load_body(res).await.unwrap().is_empty()); + + let res = srv.get("/cl-body").send().await.unwrap(); + assert_eq!(res.status(), http::StatusCode::NOT_MODIFIED); + assert_eq!( + res.headers().get(&CL), + Some(&header::HeaderValue::from_static("4")), + ); + // server does not prevent payload from being sent but clients may choose not to read it + // TODO: this is probably a bug, especially since CL header can differ in length from the body + assert!(!srv.load_body(res).await.unwrap().is_empty()); + + // TODO: add stream response tests + + srv.stop().await; +} diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index cf5738aa0..6c776a871 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -520,12 +520,12 @@ impl TestServer { self.client.headers() } - /// Gracefully stop HTTP server. + /// Stop HTTP server. /// - /// Waits for spawned `Server` and `System` to shutdown gracefully. + /// Waits for spawned `Server` and `System` to shutdown (force) shutdown. pub async fn stop(mut self) { // signal server to stop - self.server.stop(true).await; + self.server.stop(false).await; // also signal system to stop // though this is handled by `ServerBuilder::exit_system` too From dd347e0bd069f00c67992fdcf805a21a8c2274cc Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 22 Nov 2021 09:19:09 +0800 Subject: [PATCH 107/861] implement io-uring for actix-files (#2408) Co-authored-by: Rob Ede --- .cargo/config.toml | 22 ++- .github/workflows/ci.yml | 53 ++++--- Cargo.toml | 5 +- actix-files/CHANGES.md | 6 + actix-files/Cargo.toml | 7 +- actix-files/src/chunked.rs | 294 +++++++++++++++++++++++++++++------- actix-files/src/files.rs | 22 ++- actix-files/src/lib.rs | 105 +++++++------ actix-files/src/named.rs | 170 +++++++++++++++------ actix-files/src/path_buf.rs | 2 +- actix-files/src/service.rs | 220 +++++++++++++++------------ actix-http-test/CHANGES.md | 3 + actix-http-test/src/lib.rs | 31 ++-- actix-test/CHANGES.md | 3 + actix-test/src/lib.rs | 281 ++++++++++++++++++---------------- src/middleware/compress.rs | 3 +- 16 files changed, 794 insertions(+), 433 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 40a513efd..606c30de7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,14 +1,12 @@ [alias] -chk = "check --workspace --all-features --tests --examples --bins" -lint = "clippy --workspace --all-features --tests --examples --bins" -ci-min = "hack check --workspace --no-default-features" -ci-min-test = "hack check --workspace --no-default-features --tests --examples" -ci-default = "check --workspace --bins --tests --examples" -ci-full = "check --workspace --all-features --bins --tests --examples" -ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" -ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" +lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo" +lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo" -ci-feature-powerset-check-no-tls="hack --workspace --feature-powerset --skip=__compress,rustls,openssl check" -ci-feature-powerset-check-rustls="hack --workspace --feature-powerset --features=rustls --skip=__compress,openssl check" -ci-feature-powerset-check-openssl="hack --workspace --feature-powerset --features=openssl --skip=__compress,rustls check" -ci-feature-powerset-check-all="hack --workspace --feature-powerset --skip=__compress check" +# lib checking +ci-check-min = "hack --workspace check --no-default-features" +ci-check-default = "hack --workspace check" +ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check" +ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check" + +# testing +ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f586d8d8..38c066d6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,26 +62,34 @@ jobs: - name: check minimal uses: actions-rs/cargo@v1 - with: { command: ci-min } - - - name: check minimal + tests - uses: actions-rs/cargo@v1 - with: { command: ci-min-test } + with: { command: ci-check-min } - name: check default uses: actions-rs/cargo@v1 - with: { command: ci-default } - - - name: check full - uses: actions-rs/cargo@v1 - with: { command: ci-full } + with: { command: ci-check-default } - name: tests - uses: actions-rs/cargo@v1 timeout-minutes: 60 - with: - command: ci-test - args: --skip=test_reading_deflate_encoding_large_random_rustls + run: | + cargo test --lib --tests -p=actix-router --all-features + cargo test --lib --tests -p=actix-http --all-features + cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls + cargo test --lib --tests -p=actix-web-codegen --all-features + cargo test --lib --tests -p=awc --all-features + cargo test --lib --tests -p=actix-http-test --all-features + cargo test --lib --tests -p=actix-test --all-features + cargo test --lib --tests -p=actix-files + cargo test --lib --tests -p=actix-multipart --all-features + cargo test --lib --tests -p=actix-web-actors --all-features + + - name: tests (io-uring) + if: matrix.target.os == 'ubuntu-latest' + timeout-minutes: 60 + run: > + sudo bash -c "ulimit -Sl 512 + && ulimit -Hl 512 + && PATH=$PATH:/usr/share/rust/.cargo/bin + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - name: Clear the cargo caches run: | @@ -114,9 +122,12 @@ jobs: args: cargo-hack - name: check feature combinations - # if: github.ref == 'refs/heads/master' uses: actions-rs/cargo@v1 - with: { command: ci-feature-powerset-check-all } + with: { command: ci-check-all-feature-powerset } + + - name: check feature combinations + uses: actions-rs/cargo@v1 + with: { command: ci-check-all-feature-powerset-linux } coverage: name: coverage @@ -166,11 +177,11 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack + # - name: Install cargo-hack + # uses: actions-rs/cargo@v1 + # with: + # command: install + # args: cargo-hack - name: doc tests uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index 3f1f54fcc..537d1b5fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,10 +65,13 @@ rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] +# io-uring feature only avaiable for Linux OSes. +experimental-io-uring = ["actix-server/io-uring"] + [dependencies] actix-codec = "0.4.1" actix-macros = "0.2.3" -actix-rt = "2.2" +actix-rt = "2.3" actix-server = "2.0.0-beta.9" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 41336c21c..7da775607 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +* Add `NamedFile::open_async`. [#2408] * Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +* Add `impl Clone` for `FilesService`. [#2408] +[#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bbb9f551a..c0ff18678 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -14,11 +14,13 @@ edition = "2018" name = "actix_files" path = "src/lib.rs" +[features] +experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] + [dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false } actix-http = "3.0.0-beta.12" actix-service = "2.0.0" -actix-utils = "3.0.0" askama_escape = "0.10" bitflags = "1" @@ -30,6 +32,9 @@ log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" +pin-project-lite = "0.2.7" + +tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index f639848c9..fbb46e417 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -1,98 +1,278 @@ use std::{ cmp, fmt, - fs::File, future::Future, - io::{self, Read, Seek}, + io, pin::Pin, task::{Context, Poll}, }; -use actix_web::{ - error::{BlockingError, Error}, - rt::task::{spawn_blocking, JoinHandle}, -}; +use actix_web::error::Error; use bytes::Bytes; use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; -#[doc(hidden)] -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `ThreadPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - state: ChunkedReadFileState, - counter: u64, -} +use super::named::File; -enum ChunkedReadFileState { - File(Option), - Future(JoinHandle>), -} - -impl ChunkedReadFile { - pub(crate) fn new(size: u64, offset: u64, file: File) -> Self { - Self { - size, - offset, - state: ChunkedReadFileState::File(Some(file)), - counter: 0, - } +pin_project! { + /// Adapter to read a `std::file::File` in chunks. + #[doc(hidden)] + pub struct ChunkedReadFile { + size: u64, + offset: u64, + #[pin] + state: ChunkedReadFileState, + counter: u64, + callback: F, } } -impl fmt::Debug for ChunkedReadFile { +#[cfg(not(feature = "experimental-io-uring"))] +pin_project! { + #[project = ChunkedReadFileStateProj] + #[project_replace = ChunkedReadFileStateProjReplace] + enum ChunkedReadFileState { + File { file: Option, }, + Future { #[pin] fut: Fut }, + } +} + +#[cfg(feature = "experimental-io-uring")] +pin_project! { + #[project = ChunkedReadFileStateProj] + #[project_replace = ChunkedReadFileStateProjReplace] + enum ChunkedReadFileState { + File { file: Option<(File, BytesMut)> }, + Future { #[pin] fut: Fut }, + } +} + +impl fmt::Debug for ChunkedReadFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("ChunkedReadFile") } } -impl Stream for ChunkedReadFile { +pub(crate) fn new_chunked_read( + size: u64, + offset: u64, + file: File, +) -> impl Stream> { + ChunkedReadFile { + size, + offset, + #[cfg(not(feature = "experimental-io-uring"))] + state: ChunkedReadFileState::File { file: Some(file) }, + #[cfg(feature = "experimental-io-uring")] + state: ChunkedReadFileState::File { + file: Some((file, BytesMut::new())), + }, + counter: 0, + callback: chunked_read_file_callback, + } +} + +#[cfg(not(feature = "experimental-io-uring"))] +async fn chunked_read_file_callback( + mut file: File, + offset: u64, + max_bytes: usize, +) -> Result<(File, Bytes), Error> { + use io::{Read as _, Seek as _}; + + let res = actix_web::rt::task::spawn_blocking(move || { + let mut buf = Vec::with_capacity(max_bytes); + + file.seek(io::SeekFrom::Start(offset))?; + + let n_bytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; + + if n_bytes == 0 { + Err(io::Error::from(io::ErrorKind::UnexpectedEof)) + } else { + Ok((file, Bytes::from(buf))) + } + }) + .await + .map_err(|_| actix_web::error::BlockingError)??; + + Ok(res) +} + +#[cfg(feature = "experimental-io-uring")] +async fn chunked_read_file_callback( + file: File, + offset: u64, + max_bytes: usize, + mut bytes_mut: BytesMut, +) -> io::Result<(File, Bytes, BytesMut)> { + bytes_mut.reserve(max_bytes); + + let (res, mut bytes_mut) = file.read_at(bytes_mut, offset).await; + let n_bytes = res?; + + if n_bytes == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + + let bytes = bytes_mut.split_to(n_bytes).freeze(); + + Ok((file, bytes, bytes_mut)) +} + +#[cfg(feature = "experimental-io-uring")] +impl Stream for ChunkedReadFile +where + F: Fn(File, u64, usize, BytesMut) -> Fut, + Fut: Future>, +{ type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.as_mut().get_mut(); - match this.state { - ChunkedReadFileState::File(ref mut file) => { - let size = this.size; - let offset = this.offset; - let counter = this.counter; + let mut this = self.as_mut().project(); + match this.state.as_mut().project() { + ChunkedReadFileStateProj::File { file } => { + let size = *this.size; + let offset = *this.offset; + let counter = *this.counter; if size == counter { Poll::Ready(None) } else { - let mut file = file + let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + + let (file, bytes_mut) = file .take() .expect("ChunkedReadFile polled after completion"); - let fut = spawn_blocking(move || { - let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + let fut = (this.callback)(file, offset, max_bytes, bytes_mut); - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; + this.state + .project_replace(ChunkedReadFileState::Future { fut }); - let n_bytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - - if n_bytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - - Ok((file, Bytes::from(buf))) - }); - this.state = ChunkedReadFileState::Future(fut); self.poll_next(cx) } } - ChunkedReadFileState::Future(ref mut fut) => { - let (file, bytes) = - ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; - this.state = ChunkedReadFileState::File(Some(file)); + ChunkedReadFileStateProj::Future { fut } => { + let (file, bytes, bytes_mut) = ready!(fut.poll(cx))?; - this.offset += bytes.len() as u64; - this.counter += bytes.len() as u64; + this.state.project_replace(ChunkedReadFileState::File { + file: Some((file, bytes_mut)), + }); + + *this.offset += bytes.len() as u64; + *this.counter += bytes.len() as u64; Poll::Ready(Some(Ok(bytes))) } } } } + +#[cfg(not(feature = "experimental-io-uring"))] +impl Stream for ChunkedReadFile +where + F: Fn(File, u64, usize) -> Fut, + Fut: Future>, +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.as_mut().project(); + match this.state.as_mut().project() { + ChunkedReadFileStateProj::File { file } => { + let size = *this.size; + let offset = *this.offset; + let counter = *this.counter; + + if size == counter { + Poll::Ready(None) + } else { + let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + + let file = file + .take() + .expect("ChunkedReadFile polled after completion"); + + let fut = (this.callback)(file, offset, max_bytes); + + this.state + .project_replace(ChunkedReadFileState::Future { fut }); + + self.poll_next(cx) + } + } + ChunkedReadFileStateProj::Future { fut } => { + let (file, bytes) = ready!(fut.poll(cx))?; + + this.state + .project_replace(ChunkedReadFileState::File { file: Some(file) }); + + *this.offset += bytes.len() as u64; + *this.counter += bytes.len() as u64; + + Poll::Ready(Some(Ok(bytes))) + } + } + } +} + +#[cfg(feature = "experimental-io-uring")] +use bytes_mut::BytesMut; + +// TODO: remove new type and use bytes::BytesMut directly +#[doc(hidden)] +#[cfg(feature = "experimental-io-uring")] +mod bytes_mut { + use std::ops::{Deref, DerefMut}; + + use tokio_uring::buf::{IoBuf, IoBufMut}; + + #[derive(Debug)] + pub struct BytesMut(bytes::BytesMut); + + impl BytesMut { + pub(super) fn new() -> Self { + Self(bytes::BytesMut::new()) + } + } + + impl Deref for BytesMut { + type Target = bytes::BytesMut; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for BytesMut { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + unsafe impl IoBuf for BytesMut { + fn stable_ptr(&self) -> *const u8 { + self.0.as_ptr() + } + + fn bytes_init(&self) -> usize { + self.0.len() + } + + fn bytes_total(&self) -> usize { + self.0.capacity() + } + } + + unsafe impl IoBufMut for BytesMut { + fn stable_mut_ptr(&mut self) -> *mut u8 { + self.0.as_mut_ptr() + } + + unsafe fn set_init(&mut self, init_len: usize) { + if self.len() < init_len { + self.0.set_len(init_len); + } + } + } +} diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 68879822a..06909bf08 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -6,7 +6,6 @@ use std::{ }; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; -use actix_utils::future::ok; use actix_web::{ dev::{ AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, @@ -20,8 +19,9 @@ use actix_web::{ use futures_core::future::LocalBoxFuture; use crate::{ - directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, - MimeOverride, PathFilter, + directory_listing, named, + service::{FilesService, FilesServiceInner}, + Directory, DirectoryRenderer, HttpNewService, MimeOverride, PathFilter, }; /// Static files handling service. @@ -283,11 +283,17 @@ impl Files { /// Setting a fallback static file handler: /// ``` /// use actix_files::{Files, NamedFile}; + /// use actix_web::dev::{ServiceRequest, ServiceResponse, fn_service}; /// /// # fn run() -> Result<(), actix_web::Error> { /// let files = Files::new("/", "./static") /// .index_file("index.html") - /// .default_handler(NamedFile::open("./static/404.html")?); + /// .default_handler(fn_service(|req: ServiceRequest| async { + /// let (req, _) = req.into_parts(); + /// let file = NamedFile::open_async("./static/404.html").await?; + /// let res = file.into_response(&req); + /// Ok(ServiceResponse::new(req, res)) + /// })); /// # Ok(()) /// # } /// ``` @@ -353,7 +359,7 @@ impl ServiceFactory for Files { type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - let mut srv = FilesService { + let mut inner = FilesServiceInner { directory: self.directory.clone(), index: self.index.clone(), show_index: self.show_index, @@ -372,14 +378,14 @@ impl ServiceFactory for Files { Box::pin(async { match fut.await { Ok(default) => { - srv.default = Some(default); - Ok(srv) + inner.default = Some(default); + Ok(FilesService(Rc::new(inner))) } Err(_) => Err(()), } }) } else { - Box::pin(ok(srv)) + Box::pin(async move { Ok(FilesService(Rc::new(inner))) }) } } } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 175c6eaee..3af5282f1 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -33,12 +33,12 @@ mod path_buf; mod range; mod service; -pub use crate::chunked::ChunkedReadFile; -pub use crate::directory::Directory; -pub use crate::files::Files; -pub use crate::named::NamedFile; -pub use crate::range::HttpRange; -pub use crate::service::FilesService; +pub use self::chunked::ChunkedReadFile; +pub use self::directory::Directory; +pub use self::files::Files; +pub use self::named::NamedFile; +pub use self::range::HttpRange; +pub use self::service::FilesService; use self::directory::{directory_listing, DirectoryRenderer}; use self::error::FilesError; @@ -62,13 +62,12 @@ type PathFilter = dyn Fn(&Path, &RequestHead) -> bool; #[cfg(test)] mod tests { use std::{ - fs::{self, File}, + fs::{self}, ops::Add, time::{Duration, SystemTime}, }; use actix_service::ServiceFactory; - use actix_utils::future::ok; use actix_web::{ guard, http::{ @@ -82,6 +81,7 @@ mod tests { }; use super::*; + use crate::named::File; #[actix_web::test] async fn test_file_extension_to_mime() { @@ -100,7 +100,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_without_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() @@ -112,7 +112,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_without_if_none_match_same() { - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let since = file.last_modified().unwrap(); let req = TestRequest::default() @@ -124,7 +124,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_with_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() @@ -137,7 +137,7 @@ mod tests { #[actix_rt::test] async fn test_if_unmodified_since() { - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let since = file.last_modified().unwrap(); let req = TestRequest::default() @@ -149,7 +149,7 @@ mod tests { #[actix_rt::test] async fn test_if_unmodified_since_failed() { - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let since = header::HttpDate::from(SystemTime::UNIX_EPOCH); let req = TestRequest::default() @@ -161,8 +161,8 @@ mod tests { #[actix_rt::test] async fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml").unwrap(); + assert!(NamedFile::open_async("test--").await.is_err()); + let mut file = NamedFile::open_async("Cargo.toml").await.unwrap(); { file.file(); let _f: &File = &file; @@ -185,8 +185,8 @@ mod tests { #[actix_rt::test] async fn test_named_file_content_disposition() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml").unwrap(); + assert!(NamedFile::open_async("test--").await.is_err()); + let mut file = NamedFile::open_async("Cargo.toml").await.unwrap(); { file.file(); let _f: &File = &file; @@ -202,7 +202,8 @@ mod tests { "inline; filename=\"Cargo.toml\"" ); - let file = NamedFile::open("Cargo.toml") + let file = NamedFile::open_async("Cargo.toml") + .await .unwrap() .disable_content_disposition(); let req = TestRequest::default().to_http_request(); @@ -212,8 +213,19 @@ mod tests { #[actix_rt::test] async fn test_named_file_non_ascii_file_name() { - let mut file = - NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap(); + let file = { + #[cfg(feature = "experimental-io-uring")] + { + crate::named::File::open("Cargo.toml").await.unwrap() + } + + #[cfg(not(feature = "experimental-io-uring"))] + { + crate::named::File::open("Cargo.toml").unwrap() + } + }; + + let mut file = NamedFile::from_file(file, "貨物.toml").unwrap(); { file.file(); let _f: &File = &file; @@ -236,7 +248,8 @@ mod tests { #[actix_rt::test] async fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") + let mut file = NamedFile::open_async("Cargo.toml") + .await .unwrap() .set_content_type(mime::TEXT_XML); { @@ -261,7 +274,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png").unwrap(); + let mut file = NamedFile::open_async("tests/test.png").await.unwrap(); { file.file(); let _f: &File = &file; @@ -284,7 +297,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_javascript() { - let file = NamedFile::open("tests/test.js").unwrap(); + let file = NamedFile::open_async("tests/test.js").await.unwrap(); let req = TestRequest::default().to_http_request(); let resp = file.respond_to(&req).await.unwrap(); @@ -304,7 +317,8 @@ mod tests { disposition: DispositionType::Attachment, parameters: vec![DispositionParam::Filename(String::from("test.png"))], }; - let mut file = NamedFile::open("tests/test.png") + let mut file = NamedFile::open_async("tests/test.png") + .await .unwrap() .set_content_disposition(cd); { @@ -329,7 +343,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary").unwrap(); + let mut file = NamedFile::open_async("tests/test.binary").await.unwrap(); { file.file(); let _f: &File = &file; @@ -352,7 +366,8 @@ mod tests { #[actix_rt::test] async fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") + let mut file = NamedFile::open_async("Cargo.toml") + .await .unwrap() .set_status_code(StatusCode::NOT_FOUND); { @@ -568,7 +583,8 @@ mod tests { async fn test_named_file_content_encoding() { let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { - NamedFile::open("Cargo.toml") + NamedFile::open_async("Cargo.toml") + .await .unwrap() .set_content_encoding(header::ContentEncoding::Identity) }), @@ -588,7 +604,8 @@ mod tests { async fn test_named_file_content_encoding_gzip() { let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { - NamedFile::open("Cargo.toml") + NamedFile::open_async("Cargo.toml") + .await .unwrap() .set_content_encoding(header::ContentEncoding::Gzip) }), @@ -614,7 +631,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_allowed_method() { let req = TestRequest::default().method(Method::GET).to_http_request(); - let file = NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open_async("Cargo.toml").await.unwrap(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } @@ -705,8 +722,8 @@ mod tests { #[actix_rt::test] async fn test_default_handler_file_missing() { let st = Files::new("/", ".") - .default_handler(|req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().body("default content"))) + .default_handler(|req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().body("default content"))) }) .new_service(()) .await @@ -789,9 +806,8 @@ mod tests { #[actix_rt::test] async fn test_serve_named_file() { - let srv = - test::init_service(App::new().service(NamedFile::open("Cargo.toml").unwrap())) - .await; + let factory = NamedFile::open_async("Cargo.toml").await.unwrap(); + let srv = test::init_service(App::new().service(factory)).await; let req = TestRequest::get().uri("/Cargo.toml").to_request(); let res = test::call_service(&srv, req).await; @@ -808,11 +824,9 @@ mod tests { #[actix_rt::test] async fn test_serve_named_file_prefix() { - let srv = test::init_service( - App::new() - .service(web::scope("/test").service(NamedFile::open("Cargo.toml").unwrap())), - ) - .await; + let factory = NamedFile::open_async("Cargo.toml").await.unwrap(); + let srv = + test::init_service(App::new().service(web::scope("/test").service(factory))).await; let req = TestRequest::get().uri("/test/Cargo.toml").to_request(); let res = test::call_service(&srv, req).await; @@ -829,10 +843,8 @@ mod tests { #[actix_rt::test] async fn test_named_file_default_service() { - let srv = test::init_service( - App::new().default_service(NamedFile::open("Cargo.toml").unwrap()), - ) - .await; + let factory = NamedFile::open_async("Cargo.toml").await.unwrap(); + let srv = test::init_service(App::new().default_service(factory)).await; for route in ["/foobar", "/baz", "/"].iter() { let req = TestRequest::get().uri(route).to_request(); @@ -847,8 +859,9 @@ mod tests { #[actix_rt::test] async fn test_default_handler_named_file() { + let factory = NamedFile::open_async("Cargo.toml").await.unwrap(); let st = Files::new("/", ".") - .default_handler(NamedFile::open("Cargo.toml").unwrap()) + .default_handler(factory) .new_service(()) .await .unwrap(); @@ -926,8 +939,8 @@ mod tests { #[actix_rt::test] async fn test_default_handler_filter() { let st = Files::new("/", ".") - .default_handler(|req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().body("default content"))) + .default_handler(|req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().body("default content"))) }) .path_filter(|path, _| path.extension() == Some("png".as_ref())) .new_service(()) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index dac548708..547048bbd 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,5 +1,6 @@ use std::{ - fs::{File, Metadata}, + fmt, + fs::Metadata, io, ops::{Deref, DerefMut}, path::{Path, PathBuf}, @@ -11,7 +12,6 @@ use std::os::unix::fs::MetadataExt; use actix_http::body::AnyBody; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, ready, Ready}; use actix_web::{ dev::{ AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest, @@ -26,9 +26,9 @@ use actix_web::{ Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; +use futures_core::future::LocalBoxFuture; use mime_guess::from_path; -use crate::ChunkedReadFile; use crate::{encoding::equiv_utf8_text, range::HttpRange}; bitflags! { @@ -53,9 +53,9 @@ impl Default for Flags { /// use actix_web::App; /// use actix_files::NamedFile; /// -/// # fn run() -> Result<(), Box> { -/// let app = App::new() -/// .service(NamedFile::open("./static/index.html")?); +/// # async fn run() -> Result<(), Box> { +/// let file = NamedFile::open_async("./static/index.html").await?; +/// let app = App::new().service(file); /// # Ok(()) /// # } /// ``` @@ -67,10 +67,9 @@ impl Default for Flags { /// /// #[get("/")] /// async fn index() -> impl Responder { -/// NamedFile::open("./static/index.html") +/// NamedFile::open_async("./static/index.html").await /// } /// ``` -#[derive(Debug)] pub struct NamedFile { path: PathBuf, file: File, @@ -83,6 +82,37 @@ pub struct NamedFile { pub(crate) encoding: Option, } +impl fmt::Debug for NamedFile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NamedFile") + .field("path", &self.path) + .field( + "file", + #[cfg(feature = "experimental-io-uring")] + { + &"tokio_uring::File" + }, + #[cfg(not(feature = "experimental-io-uring"))] + { + &self.file + }, + ) + .field("modified", &self.modified) + .field("md", &self.md) + .field("flags", &self.flags) + .field("status_code", &self.status_code) + .field("content_type", &self.content_type) + .field("content_disposition", &self.content_disposition) + .field("encoding", &self.encoding) + .finish() + } +} + +#[cfg(not(feature = "experimental-io-uring"))] +pub(crate) use std::fs::File; +#[cfg(feature = "experimental-io-uring")] +pub(crate) use tokio_uring::fs::File; + impl NamedFile { /// Creates an instance from a previously opened file. /// @@ -90,8 +120,7 @@ impl NamedFile { /// `ContentDisposition` headers. /// /// # Examples - /// - /// ``` + /// ```ignore /// use actix_files::NamedFile; /// use std::io::{self, Write}; /// use std::env; @@ -152,7 +181,30 @@ impl NamedFile { (ct, cd) }; - let md = file.metadata()?; + let md = { + #[cfg(not(feature = "experimental-io-uring"))] + { + file.metadata()? + } + + #[cfg(feature = "experimental-io-uring")] + { + use std::os::unix::prelude::{AsRawFd, FromRawFd}; + + let fd = file.as_raw_fd(); + + // SAFETY: fd is borrowed and lives longer than the unsafe block + unsafe { + let file = std::fs::File::from_raw_fd(fd); + let md = file.metadata(); + // SAFETY: forget the fd before exiting block in success or error case but don't + // run destructor (that would close file handle) + std::mem::forget(file); + md? + } + } + }; + let modified = md.modified().ok(); let encoding = None; @@ -169,17 +221,45 @@ impl NamedFile { }) } + #[cfg(not(feature = "experimental-io-uring"))] /// Attempts to open a file in read-only mode. /// /// # Examples - /// /// ``` /// use actix_files::NamedFile; - /// /// let file = NamedFile::open("foo.txt"); /// ``` pub fn open>(path: P) -> io::Result { - Self::from_file(File::open(&path)?, path) + let file = File::open(&path)?; + Self::from_file(file, path) + } + + /// Attempts to open a file asynchronously in read-only mode. + /// + /// When the `experimental-io-uring` crate feature is enabled, this will be async. + /// Otherwise, it will be just like [`open`][Self::open]. + /// + /// # Examples + /// ``` + /// use actix_files::NamedFile; + /// # async fn open() { + /// let file = NamedFile::open_async("foo.txt").await.unwrap(); + /// # } + /// ``` + pub async fn open_async>(path: P) -> io::Result { + let file = { + #[cfg(not(feature = "experimental-io-uring"))] + { + File::open(&path)? + } + + #[cfg(feature = "experimental-io-uring")] + { + File::open(&path).await? + } + }; + + Self::from_file(file, path) } /// Returns reference to the underlying `File` object. @@ -191,13 +271,12 @@ impl NamedFile { /// Retrieve the path of this file. /// /// # Examples - /// /// ``` /// # use std::io; /// use actix_files::NamedFile; /// - /// # fn path() -> io::Result<()> { - /// let file = NamedFile::open("test.txt")?; + /// # async fn path() -> io::Result<()> { + /// let file = NamedFile::open_async("test.txt").await?; /// assert_eq!(file.path().as_os_str(), "foo.txt"); /// # Ok(()) /// # } @@ -337,7 +416,7 @@ impl NamedFile { res.encoding(current_encoding); } - let reader = ChunkedReadFile::new(self.md.len(), 0, self.file); + let reader = super::chunked::new_chunked_read(self.md.len(), 0, self.file); return res.streaming(reader); } @@ -451,7 +530,7 @@ impl NamedFile { return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None); } - let reader = ChunkedReadFile::new(length, offset, self.file); + let reader = super::chunked::new_chunked_read(length, offset, self.file); if offset != 0 || length != self.md.len() { resp.status(StatusCode::PARTIAL_CONTENT); @@ -461,20 +540,6 @@ impl NamedFile { } } -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - /// Returns true if `req` has no `If-Match` header or one which matches `etag`. fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { @@ -515,6 +580,20 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } +impl Deref for NamedFile { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.file + } +} + +impl DerefMut for NamedFile { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.file + } +} + impl Responder for NamedFile { fn respond_to(self, req: &HttpRequest) -> HttpResponse { self.into_response(req) @@ -525,14 +604,16 @@ impl ServiceFactory for NamedFile { type Response = ServiceResponse; type Error = Error; type Config = (); - type InitError = (); type Service = NamedFileService; - type Future = Ready>; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - ok(NamedFileService { + let service = NamedFileService { path: self.path.clone(), - }) + }; + + Box::pin(async move { Ok(service) }) } } @@ -545,18 +626,19 @@ pub struct NamedFileService { impl Service for NamedFileService { type Response = ServiceResponse; type Error = Error; - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); fn call(&self, req: ServiceRequest) -> Self::Future { let (req, _) = req.into_parts(); - ready( - NamedFile::open(&self.path) - .map_err(|e| e.into()) - .map(|f| f.into_response(&req)) - .map(|res| ServiceResponse::new(req, res)), - ) + + let path = self.path.clone(); + Box::pin(async move { + let file = NamedFile::open_async(path).await?; + let res = file.into_response(&req); + Ok(ServiceResponse::new(req, res)) + }) } } diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 0e0d4f51d..8c8bca6ce 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -1,9 +1,9 @@ use std::{ + future::{ready, Ready}, path::{Path, PathBuf}, str::FromStr, }; -use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, FromRequest, HttpRequest}; use crate::error::UriSegmentError; diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 09122c63e..f6e1c2e11 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,7 +1,6 @@ -use std::{fmt, io, path::PathBuf, rc::Rc}; +use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc}; use actix_service::Service; -use actix_utils::future::ok; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, error::Error, @@ -17,7 +16,18 @@ use crate::{ }; /// Assembled file serving service. -pub struct FilesService { +#[derive(Clone)] +pub struct FilesService(pub(crate) Rc); + +impl Deref for FilesService { + type Target = FilesServiceInner; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +pub struct FilesServiceInner { pub(crate) directory: PathBuf, pub(crate) index: Option, pub(crate) show_index: bool, @@ -31,20 +41,50 @@ pub struct FilesService { pub(crate) hidden_files: bool, } +impl fmt::Debug for FilesServiceInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("FilesServiceInner") + } +} + impl FilesService { - fn handle_err( + async fn handle_err( &self, err: io::Error, req: ServiceRequest, - ) -> LocalBoxFuture<'static, Result> { + ) -> Result { log::debug!("error handling {}: {}", req.path(), err); if let Some(ref default) = self.default { - Box::pin(default.call(req)) + default.call(req).await } else { - Box::pin(ok(req.error_response(err))) + Ok(req.error_response(err)) } } + + fn serve_named_file( + &self, + req: ServiceRequest, + mut named_file: NamedFile, + ) -> ServiceResponse { + if let Some(ref mime_override) = self.mime_override { + let new_disposition = mime_override(&named_file.content_type.type_()); + named_file.content_disposition.disposition = new_disposition; + } + named_file.flags = self.file_flags; + + let (req, _) = req.into_parts(); + let res = named_file.into_response(&req); + ServiceResponse::new(req, res) + } + + fn show_index(&self, req: ServiceRequest, path: PathBuf) -> ServiceResponse { + let dir = Directory::new(self.directory.clone(), path); + + let (req, _) = req.into_parts(); + + (self.renderer)(&dir, &req).unwrap_or_else(|e| ServiceResponse::from_err(e, req)) + } } impl fmt::Debug for FilesService { @@ -56,7 +96,7 @@ impl fmt::Debug for FilesService { impl Service for FilesService { type Response = ServiceResponse; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); @@ -69,103 +109,87 @@ impl Service for FilesService { matches!(*req.method(), Method::HEAD | Method::GET) }; - if !is_method_valid { - return Box::pin(ok(req.into_response( - actix_web::HttpResponse::MethodNotAllowed() - .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) - .body("Request did not meet this resource's requirements."), - ))); - } + let this = self.clone(); - let real_path = - match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { - Ok(item) => item, - Err(e) => return Box::pin(ok(req.error_response(e))), - }; + Box::pin(async move { + if !is_method_valid { + return Ok(req.into_response( + actix_web::HttpResponse::MethodNotAllowed() + .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) + .body("Request did not meet this resource's requirements."), + )); + } - if let Some(filter) = &self.path_filter { - if !filter(real_path.as_ref(), req.head()) { - if let Some(ref default) = self.default { - return Box::pin(default.call(req)); - } else { - return Box::pin(ok( - req.into_response(actix_web::HttpResponse::NotFound().finish()) + let real_path = + match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) { + Ok(item) => item, + Err(e) => return Ok(req.error_response(e)), + }; + + if let Some(filter) = &this.path_filter { + if !filter(real_path.as_ref(), req.head()) { + if let Some(ref default) = this.default { + return default.call(req).await; + } else { + return Ok( + req.into_response(actix_web::HttpResponse::NotFound().finish()) + ); + } + } + } + + // full file path + let path = this.directory.join(&real_path); + if let Err(err) = path.canonicalize() { + return this.handle_err(err, req).await; + } + + if path.is_dir() { + if this.redirect_to_slash + && !req.path().ends_with('/') + && (this.index.is_some() || this.show_index) + { + let redirect_to = format!("{}/", req.path()); + + return Ok(req.into_response( + HttpResponse::Found() + .insert_header((header::LOCATION, redirect_to)) + .finish(), )); } - } - } - // full file path - let path = self.directory.join(&real_path); - if let Err(err) = path.canonicalize() { - return Box::pin(self.handle_err(err, req)); - } - - if path.is_dir() { - if self.redirect_to_slash - && !req.path().ends_with('/') - && (self.index.is_some() || self.show_index) - { - let redirect_to = format!("{}/", req.path()); - - return Box::pin(ok(req.into_response( - HttpResponse::Found() - .insert_header((header::LOCATION, redirect_to)) - .finish(), - ))); - } - - let serve_named_file = |req: ServiceRequest, mut named_file: NamedFile| { - if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); - named_file.content_disposition.disposition = new_disposition; - } - named_file.flags = self.file_flags; - - let (req, _) = req.into_parts(); - let res = named_file.into_response(&req); - Box::pin(ok(ServiceResponse::new(req, res))) - }; - - let show_index = |req: ServiceRequest| { - let dir = Directory::new(self.directory.clone(), path.clone()); - - let (req, _) = req.into_parts(); - let x = (self.renderer)(&dir, &req); - - Box::pin(match x { - Ok(resp) => ok(resp), - Err(err) => ok(ServiceResponse::from_err(err, req)), - }) - }; - - match self.index { - Some(ref index) => match NamedFile::open(path.join(index)) { - Ok(named_file) => serve_named_file(req, named_file), - Err(_) if self.show_index => show_index(req), - Err(err) => self.handle_err(err, req), - }, - None if self.show_index => show_index(req), - _ => Box::pin(ok(ServiceResponse::from_err( - FilesError::IsDirectory, - req.into_parts().0, - ))), - } - } else { - match NamedFile::open(path) { - Ok(mut named_file) => { - if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); - named_file.content_disposition.disposition = new_disposition; + match this.index { + Some(ref index) => { + let named_path = path.join(index); + match NamedFile::open_async(named_path).await { + Ok(named_file) => Ok(this.serve_named_file(req, named_file)), + Err(_) if this.show_index => Ok(this.show_index(req, path)), + Err(err) => this.handle_err(err, req).await, + } } - named_file.flags = self.file_flags; - - let (req, _) = req.into_parts(); - let res = named_file.into_response(&req); - Box::pin(ok(ServiceResponse::new(req, res))) + None if this.show_index => Ok(this.show_index(req, path)), + _ => Ok(ServiceResponse::from_err( + FilesError::IsDirectory, + req.into_parts().0, + )), + } + } else { + match NamedFile::open_async(&path).await { + Ok(mut named_file) => { + if let Some(ref mime_override) = this.mime_override { + let new_disposition = + mime_override(&named_file.content_type.type_()); + named_file.content_disposition.disposition = new_disposition; + } + named_file.flags = this.file_flags; + + let (req, _) = req.into_parts(); + let res = named_file.into_response(&req); + Ok(ServiceResponse::new(req, res)) + } + Err(err) => this.handle_err(err, req).await, } - Err(err) => self.handle_err(err, req), } - } + }) } } diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index ea00acb0c..3356f5334 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] + +[#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 699bb2660..a4bc6b2bb 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -66,25 +66,24 @@ pub async fn test_server_with_addr>( // run server in separate thread thread::spawn(move || { - let sys = System::new(); - let local_addr = tcp.local_addr().unwrap(); + System::new().block_on(async move { + let local_addr = tcp.local_addr().unwrap(); - let srv = Server::build() - .workers(1) - .disable_signals() - .listen("test", tcp, factory) - .expect("test server could not be created"); + let srv = Server::build() + .workers(1) + .disable_signals() + .system_exit() + .listen("test", tcp, factory) + .expect("test server could not be created"); - let srv = srv.run(); - started_tx - .send((System::current(), srv.handle(), local_addr)) - .unwrap(); + let srv = srv.run(); + started_tx + .send((System::current(), srv.handle(), local_addr)) + .unwrap(); - // drive server loop - sys.block_on(srv).unwrap(); - - // start system event loop - sys.run().unwrap(); + // drive server loop + srv.await.unwrap(); + }); // notify TestServer that server and system have shut down // all thread managed resources should be dropped at this point diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 5c22139ae..78fd4e4ca 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] + +[#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 6c776a871..b80918ec0 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -146,156 +146,183 @@ where // run server in separate orphaned thread thread::spawn(move || { - let sys = rt::System::new(); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - let factory = factory.clone(); - let srv_cfg = cfg.clone(); - let timeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals().system_exit(); + rt::System::new().block_on(async move { + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + let factory = factory.clone(); + let srv_cfg = cfg.clone(); + let timeout = cfg.client_timeout; - let srv = match srv_cfg.stream { - StreamType::Tcp => match srv_cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let builder = Server::build().workers(1).disable_signals().system_exit(); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + let srv = match srv_cfg.stream { + StreamType::Tcp => match srv_cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h1(map_config(fac, move |_| app_cfg.clone())) - .tcp() - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(fac, move |_| app_cfg.clone())) + .tcp() + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h2(map_config(fac, move |_| app_cfg.clone())) - .tcp() - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(fac, move |_| app_cfg.clone())) + .tcp() + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .finish(map_config(fac, move |_| app_cfg.clone())) - .tcp() - }), - }, - #[cfg(feature = "openssl")] - StreamType::Openssl(acceptor) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(fac, move |_| app_cfg.clone())) + .tcp() + }), + }, + #[cfg(feature = "openssl")] + StreamType::Openssl(acceptor) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h1(map_config(fac, move |_| app_cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(fac, move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h2(map_config(fac, move |_| app_cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(fac, move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .finish(map_config(fac, move |_| app_cfg.clone())) - .openssl(acceptor.clone()) - }), - }, - #[cfg(feature = "rustls")] - StreamType::Rustls(config) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(fac, move |_| app_cfg.clone())) + .openssl(acceptor.clone()) + }), + }, + #[cfg(feature = "rustls")] + StreamType::Rustls(config) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h1(map_config(fac, move |_| app_cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h1(map_config(fac, move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .h2(map_config(fac, move |_| app_cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let app_cfg = - AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); + HttpService::build() + .client_timeout(timeout) + .h2(map_config(fac, move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = AppConfig::__priv_test_new( + false, + local_addr.to_string(), + local_addr, + ); - HttpService::build() - .client_timeout(timeout) - .finish(map_config(fac, move |_| app_cfg.clone())) - .rustls(config.clone()) - }), - }, - } - .expect("test server could not be created"); + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); - let srv = srv.run(); - started_tx - .send((System::current(), srv.handle(), local_addr)) - .unwrap(); + HttpService::build() + .client_timeout(timeout) + .finish(map_config(fac, move |_| app_cfg.clone())) + .rustls(config.clone()) + }), + }, + } + .expect("test server could not be created"); - // drive server loop - sys.block_on(srv).unwrap(); + let srv = srv.run(); + started_tx + .send((System::current(), srv.handle(), local_addr)) + .unwrap(); - // start system event loop - sys.run().unwrap(); + // drive server loop + srv.await.unwrap(); + + // notify TestServer that server and system have shut down + // all thread managed resources should be dropped at this point + }); - // notify TestServer that server and system have shut down - // all thread managed resources should be dropped at this point let _ = thread_stop_tx.send(()); }); diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 3e85cb846..d8878a82a 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -82,7 +82,8 @@ pub struct CompressMiddleware { } static SUPPORTED_ALGORITHM_NAMES: Lazy = Lazy::new(|| { - let mut encoding = vec![]; + #[allow(unused_mut)] // only unused when no compress features enabled + let mut encoding: Vec<&str> = vec![]; #[cfg(feature = "compress-brotli")] { From a2a42ec152b41655345eefacea94af0f526a6d2e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 21 Nov 2021 23:34:58 +0000 Subject: [PATCH 108/861] use anybody in doc test --- actix-http/src/body/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 724e20597..29d7593dd 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -28,15 +28,15 @@ pub use self::sized_stream::SizedStream; /// /// # Examples /// ``` -/// use actix_http::body::{Body, to_bytes}; +/// use actix_http::body::{AnyBody, to_bytes}; /// use bytes::Bytes; /// /// # async fn test_to_bytes() { -/// let body = Body::None; +/// let body = AnyBody::None; /// let bytes = to_bytes(body).await.unwrap(); /// assert!(bytes.is_empty()); /// -/// let body = Body::Bytes(Bytes::from_static(b"123")); +/// let body = AnyBody::Bytes(Bytes::from_static(b"123")); /// let bytes = to_bytes(body).await.unwrap(); /// assert_eq!(bytes, b"123"[..]); /// # } From a172f5968d828431122cab21b34ea0e5314684e8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 15:37:23 +0000 Subject: [PATCH 109/861] prepare for actix-tls v3 beta 9 (#2456) --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 2 ++ actix-http/Cargo.toml | 4 +-- actix-http/src/body/body.rs | 7 +++-- actix-http/src/body/mod.rs | 50 +++++++++++++++--------------- actix-http/src/h1/service.rs | 30 ++++++++++-------- actix-http/src/h2/service.rs | 60 +++++++++++++++++++----------------- actix-http/src/service.rs | 37 +++++++++++++--------- awc/Cargo.toml | 4 +-- 10 files changed, 109 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 537d1b5fc..2ac04a66e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ actix-rt = "2.3" actix-server = "2.0.0-beta.9" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.9", default-features = false, optional = true } actix-http = "3.0.0-beta.12" actix-router = "0.5.0-beta.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index f118d1627..c670464ac 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,7 +31,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.1" -actix-tls = "3.0.0-beta.7" +actix-tls = "3.0.0-beta.9" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.9" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 71cdd6d4c..7e7e9f4d4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `body::AnyBody::empty` for quickly creating an empty body. [#2446] +* `body::AnyBody::none` for quickly creating a "none" body. [#2456] * `impl Clone` for `body::AnyBody where S: Clone`. [#2448] * `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] @@ -21,6 +22,7 @@ * `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 +[#2456]: https://github.com/actix/actix-web/pull/2456 ## 3.0.0-beta.12 - 2021-11-15 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c2de71e10..852c5ce23 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,7 +73,7 @@ sha-1 = "0.9" smallvec = "1.6.1" # tls -actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true } +actix-tls = { version = "3.0.0-beta.9", default-features = false, optional = true } # compression brotli2 = { version="0.3.2", optional = true } @@ -83,7 +83,7 @@ zstd = { version = "0.9", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.9" actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.9", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs index c6439b559..e8861024b 100644 --- a/actix-http/src/body/body.rs +++ b/actix-http/src/body/body.rs @@ -32,9 +32,12 @@ pub enum AnyBody { } impl AnyBody { - // TODO: a None body constructor + /// Constructs a "body" representing an empty response. + pub fn none() -> Self { + Self::None + } - /// Constructs a new, empty body. + /// Constructs a new, 0-length body. pub fn empty() -> Self { Self::Bytes(Bytes::new()) } diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 29d7593dd..df6c6b08a 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -32,11 +32,11 @@ pub use self::sized_stream::SizedStream; /// use bytes::Bytes; /// /// # async fn test_to_bytes() { -/// let body = AnyBody::None; +/// let body = AnyBody::none(); /// let bytes = to_bytes(body).await.unwrap(); /// assert!(bytes.is_empty()); /// -/// let body = AnyBody::Bytes(Bytes::from_static(b"123")); +/// let body = AnyBody::copy_from_slice(b"123"); /// let bytes = to_bytes(body).await.unwrap(); /// assert_eq!(bytes, b"123"[..]); /// # } @@ -75,7 +75,7 @@ mod tests { use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use super::*; + use super::{to_bytes, AnyBody as TestAnyBody, BodySize, MessageBody as _}; impl AnyBody { pub(crate) fn get_ref(&self) -> &[u8] { @@ -87,13 +87,13 @@ mod tests { } /// AnyBody alias because rustc does not (can not?) infer the default type parameter. - type TestBody = AnyBody; + type AnyBody = TestAnyBody; #[actix_rt::test] async fn test_static_str() { - assert_eq!(TestBody::from("").size(), BodySize::Sized(0)); - assert_eq!(TestBody::from("test").size(), BodySize::Sized(4)); - assert_eq!(TestBody::from("test").get_ref(), b"test"); + assert_eq!(AnyBody::from("").size(), BodySize::Sized(0)); + assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from("test").get_ref(), b"test"); assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!( @@ -107,14 +107,14 @@ mod tests { #[actix_rt::test] async fn test_static_bytes() { - assert_eq!(TestBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( - TestBody::copy_from_slice(b"test".as_ref()).size(), + AnyBody::copy_from_slice(b"test".as_ref()).size(), BodySize::Sized(4) ); assert_eq!( - TestBody::copy_from_slice(b"test".as_ref()).get_ref(), + AnyBody::copy_from_slice(b"test".as_ref()).get_ref(), b"test" ); let sb = Bytes::from(&b"test"[..]); @@ -129,8 +129,8 @@ mod tests { #[actix_rt::test] async fn test_vec() { - assert_eq!(TestBody::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(Vec::from("test")).get_ref(), b"test"); + assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test"); let test_vec = Vec::from("test"); pin!(test_vec); @@ -147,8 +147,8 @@ mod tests { #[actix_rt::test] async fn test_bytes() { let b = Bytes::from("test"); - assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -161,8 +161,8 @@ mod tests { #[actix_rt::test] async fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -175,10 +175,10 @@ mod tests { #[actix_rt::test] async fn test_string() { let b = "test".to_owned(); - assert_eq!(TestBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(b.clone()).get_ref(), b"test"); - assert_eq!(TestBody::from(&b).size(), BodySize::Sized(4)); - assert_eq!(TestBody::from(&b).get_ref(), b"test"); + assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); + assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4)); + assert_eq!(AnyBody::from(&b).get_ref(), b"test"); pin!(b); assert_eq!(b.size(), BodySize::Sized(4)); @@ -219,22 +219,22 @@ mod tests { #[actix_rt::test] async fn test_body_debug() { - assert!(format!("{:?}", TestBody::None).contains("Body::None")); - assert!(format!("{:?}", TestBody::from(Bytes::from_static(b"1"))).contains('1')); + assert!(format!("{:?}", AnyBody::None).contains("Body::None")); + assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1')); } #[actix_rt::test] async fn test_serde_json() { use serde_json::{json, Value}; assert_eq!( - TestBody::from( + AnyBody::from( serde_json::to_vec(&Value::String("test".to_owned())).unwrap() ) .size(), BodySize::Sized(6) ); assert_eq!( - TestBody::from( + AnyBody::from( serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap() ) .size(), diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index dbad8cfac..970c45efb 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -102,7 +102,6 @@ where mod openssl { use super::*; - use actix_service::ServiceFactoryExt; use actix_tls::accept::{ openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, TlsError, @@ -133,7 +132,7 @@ mod openssl { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create openssl based service + /// Create OpenSSL based service. pub fn openssl( self, acceptor: SslAcceptor, @@ -145,11 +144,13 @@ mod openssl { InitError = (), > { Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) - .and_then(|io: TlsStream| { + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { let peer_addr = io.get_ref().peer_addr().ok(); - ready(Ok((io, peer_addr))) + (io, peer_addr) }) .and_then(self.map_err(TlsError::Service)) } @@ -158,16 +159,17 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { - use super::*; use std::io; - use actix_service::ServiceFactoryExt; + use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ rustls::{Acceptor, ServerConfig, TlsStream}, TlsError, }; + use super::*; + impl H1Service, S, B, X, U> where S: ServiceFactory, @@ -193,7 +195,7 @@ mod rustls { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create rustls based service + /// Create Rustls based service. pub fn rustls( self, config: ServerConfig, @@ -205,11 +207,13 @@ mod rustls { InitError = (), > { Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) - .and_then(|io: TlsStream| { + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { let peer_addr = io.get_ref().0.peer_addr().ok(); - ready(Ok((io, peer_addr))) + (io, peer_addr) }) .and_then(self.map_err(TlsError::Service)) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 32dae8ac3..794397bfc 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -101,9 +101,11 @@ where #[cfg(feature = "openssl")] mod openssl { - use actix_service::{fn_factory, fn_service, ServiceFactoryExt}; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; - use actix_tls::accept::TlsError; + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + TlsError, + }; use super::*; @@ -118,7 +120,7 @@ mod openssl { B: MessageBody + 'static, B::Error: Into>, { - /// Create OpenSSL based service + /// Create OpenSSL based service. pub fn openssl( self, acceptor: SslAcceptor, @@ -130,16 +132,14 @@ mod openssl { InitError = S::InitError, > { Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) - .and_then(fn_factory(|| { - ready(Ok::<_, S::InitError>(fn_service( - |io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ready(Ok((io, peer_addr))) - }, - ))) - })) + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + (io, peer_addr) + }) .and_then(self.map_err(TlsError::Service)) } } @@ -147,12 +147,16 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { - use super::*; - use actix_service::ServiceFactoryExt; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::accept::TlsError; use std::io; + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + rustls::{Acceptor, ServerConfig, TlsStream}, + TlsError, + }; + + use super::*; + impl H2Service, S, B> where S: ServiceFactory, @@ -164,7 +168,7 @@ mod rustls { B: MessageBody + 'static, B::Error: Into>, { - /// Create Rustls based service + /// Create Rustls based service. pub fn rustls( self, mut config: ServerConfig, @@ -180,16 +184,14 @@ mod rustls { config.alpn_protocols = protos; Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) - .and_then(fn_factory(|| { - ready(Ok::<_, S::InitError>(fn_service( - |io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ready(Ok((io, peer_addr))) - }, - ))) - })) + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + (io, peer_addr) + }) .and_then(self.map_err(TlsError::Service)) } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 62c968870..aa3e54a84 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -195,9 +195,11 @@ where #[cfg(feature = "openssl")] mod openssl { - use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; - use actix_tls::accept::TlsError; + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + TlsError, + }; use super::*; @@ -227,7 +229,7 @@ mod openssl { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create openssl based service + /// Create OpenSSL based service. pub fn openssl( self, acceptor: SslAcceptor, @@ -239,9 +241,11 @@ mod openssl { InitError = (), > { Acceptor::new(acceptor) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) - .and_then(|io: TlsStream| async { + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { Protocol::Http2 @@ -251,8 +255,9 @@ mod openssl { } else { Protocol::Http1 }; + let peer_addr = io.get_ref().peer_addr().ok(); - Ok((io, proto, peer_addr)) + (io, proto, peer_addr) }) .and_then(self.map_err(TlsError::Service)) } @@ -263,11 +268,13 @@ mod openssl { mod rustls { use std::io; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::accept::TlsError; + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + rustls::{Acceptor, ServerConfig, TlsStream}, + TlsError, + }; use super::*; - use actix_service::ServiceFactoryExt; impl HttpService, S, B, X, U> where @@ -295,7 +302,7 @@ mod rustls { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create rustls based service + /// Create Rustls based service. pub fn rustls( self, mut config: ServerConfig, @@ -311,8 +318,10 @@ mod rustls { config.alpn_protocols = protos; Acceptor::new(config) - .map_err(TlsError::Tls) - .map_init_err(|_| panic!()) + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) .and_then(|io: TlsStream| async { let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 048fe78d7..c40621597 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -57,7 +57,7 @@ actix-codec = "0.4.1" actix-service = "2.0.0" actix-http = "3.0.0-beta.12" actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.0.0-beta.7", features = ["connect"] } +actix-tls = { version = "3.0.0-beta.9", features = ["connect"] } actix-utils = "3.0.0" ahash = "0.7" @@ -93,7 +93,7 @@ actix-http = { version = "3.0.0-beta.12", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.9" -actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-beta.9", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } brotli2 = "0.3.2" From e7987e74293f86bf511d83baca23735df5f3d00c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 23 Nov 2021 02:16:56 +0800 Subject: [PATCH 110/861] awc: support http2 over plain tcp with feature flag (#2439) Co-authored-by: Rob Ede --- actix-http-test/src/lib.rs | 18 +++---- awc/Cargo.toml | 5 ++ awc/src/client/connector.rs | 101 ++++++++++++++++++++++++++++++++++-- awc/src/request.rs | 2 +- awc/tests/test_client.rs | 35 ++++++++----- awc/tests/test_connector.rs | 2 +- 6 files changed, 136 insertions(+), 27 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index a4bc6b2bb..7f55a0bf4 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -21,16 +21,15 @@ use http::Method; use socket2::{Domain, Protocol, Socket, Type}; use tokio::sync::mpsc; -/// Start test server +/// Start test server. /// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. +/// `TestServer` is very simple test server that simplify process of writing integration tests cases +/// for HTTP applications. /// /// # Examples -/// -/// ``` +/// ```no_run /// use actix_http::HttpService; -/// use actix_http_test::TestServer; +/// use actix_http_test::test_server; /// use actix_web::{web, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { @@ -39,10 +38,9 @@ use tokio::sync::mpsc; /// /// #[actix_web::test] /// async fn test_example() { -/// let mut srv = TestServer::start( -/// || HttpService::new( -/// App::new().service( -/// web::resource("/").to(my_handler)) +/// let mut srv = TestServer::start(|| +/// HttpService::new( +/// App::new().service(web::resource("/").to(my_handler)) /// ) /// ); /// diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c40621597..1dbb0ec19 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -52,6 +52,11 @@ trust-dns = ["trust-dns-resolver"] # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] +# Enable dangerous feature for testing and local network usage: +# - HTTP/2 over TCP(No Tls). +# DO NOT enable this over any internet use case. +dangerous-h2c = [] + [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 8a162c4f8..54778d31e 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -67,9 +67,9 @@ impl Connector<()> { > + Clone, > { Connector { - ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), + ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), } } @@ -189,7 +189,7 @@ where http::Version::HTTP_11 => vec![b"http/1.1".to_vec()], http::Version::HTTP_2 => vec![b"h2".to_vec(), b"http/1.1".to_vec()], _ => { - unimplemented!("actix-http:client: supported versions http/1.1, http/2") + unimplemented!("actix-http client only supports versions http/1.1 & http/2") } }; self.ssl = Connector::build_ssl(versions); @@ -279,7 +279,63 @@ where }; let tls_service = match self.ssl { - SslConnector::None => None, + SslConnector::None => { + #[cfg(not(feature = "dangerous-h2c"))] + { + None + } + #[cfg(feature = "dangerous-h2c")] + { + use std::{ + future::{ready, Ready}, + io, + }; + + use actix_tls::connect::Connection; + + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let io = self.into_parts().0; + (io, Protocol::Http2) + } + } + + /// With the `dangerous-h2c` feature enabled, this connector uses a no-op TLS + /// connection service that passes through plain TCP as a TLS connection. + /// + /// The protocol version of this fake TLS connection is set to be HTTP/2. + #[derive(Clone)] + struct NoOpTlsConnectorService; + + impl Service> for NoOpTlsConnectorService + where + U: ActixStream + 'static, + { + type Response = Connection>; + type Error = io::Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + let (io, connection) = connection.replace_io(()); + let (_, connection) = connection.replace_io(Box::new(io) as _); + + ready(Ok(connection)) + } + } + + let handshake_timeout = self.config.handshake_timeout; + + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: NoOpTlsConnectorService, + timeout: handshake_timeout, + }; + + Some(actix_service::boxed::rc_service(tls_service)) + } + } #[cfg(feature = "openssl")] SslConnector::Openssl(tls) => { const H2: &[u8] = b"h2"; @@ -760,3 +816,42 @@ mod resolver { }) } } + +#[cfg(feature = "dangerous-h2c")] +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use actix_http::{HttpService, Request, Response, Version}; + use actix_http_test::test_server; + use actix_service::ServiceFactoryExt as _; + + use super::*; + use crate::Client; + + #[actix_rt::test] + async fn h2c_connector() { + let mut srv = test_server(|| { + HttpService::build() + .h2(|_req: Request| async { Ok::<_, Infallible>(Response::ok()) }) + .tcp() + .map_err(|_| ()) + }) + .await; + + let connector = Connector { + connector: new_connector(resolver::resolver()), + config: ConnectorConfig::default(), + ssl: SslConnector::None, + }; + + let client = Client::builder().connector(connector).finish(); + + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + srv.stop().await; + } +} diff --git a/awc/src/request.rs b/awc/src/request.rs index bc3859e2e..f364b43c7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -115,10 +115,10 @@ impl ClientRequest { &self.head.method } - #[doc(hidden)] /// Set HTTP version of this request. /// /// By default requests's HTTP version depends on network stream + #[doc(hidden)] #[inline] pub fn version(mut self, version: Version) -> Self { self.head.version = version; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a0af0cab6..856a4ace2 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,20 +1,26 @@ -use std::collections::HashMap; -use std::io::{Read, Write}; -use std::net::{IpAddr, Ipv4Addr}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::time::Duration; +use std::{ + collections::HashMap, + io::{Read, Write}, + net::{IpAddr, Ipv4Addr}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::Duration, +}; use actix_utils::future::ok; -use brotli2::write::BrotliEncoder; use bytes::Bytes; use cookie::Cookie; -use flate2::read::GzDecoder; -use flate2::write::GzEncoder; -use flate2::Compression; use futures_util::stream; use rand::Rng; +#[cfg(feature = "compress-brotli")] +use brotli2::write::BrotliEncoder; + +#[cfg(feature = "compress-gzip")] +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; + use actix_http::{ http::{self, StatusCode}, HttpService, @@ -24,7 +30,6 @@ use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ dev::{AppConfig, BodyEncoding}, http::header, - middleware::Compress, web, App, Error, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; @@ -463,11 +468,12 @@ async fn test_with_query_parameter() { assert!(res.status().is_success()); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_no_decompress() { let srv = actix_test::start(|| { App::new() - .wrap(Compress::default()) + .wrap(actix_web::middleware::Compress::default()) .service(web::resource("/").route(web::to(|| { let mut res = HttpResponse::Ok().body(STR); res.encoding(header::ContentEncoding::Gzip); @@ -507,6 +513,7 @@ async fn test_no_decompress() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding() { let srv = actix_test::start(|| { @@ -530,6 +537,7 @@ async fn test_client_gzip_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding_large() { let srv = actix_test::start(|| { @@ -553,6 +561,7 @@ async fn test_client_gzip_encoding_large() { assert_eq!(bytes, Bytes::from(STR.repeat(10))); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding_large_random() { let data = rand::thread_rng() @@ -581,6 +590,7 @@ async fn test_client_gzip_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } +#[cfg(feature = "compress-brotli")] #[actix_rt::test] async fn test_client_brotli_encoding() { let srv = actix_test::start(|| { @@ -603,6 +613,7 @@ async fn test_client_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-brotli")] #[actix_rt::test] async fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 632f68b72..588c51463 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -39,7 +39,7 @@ fn tls_config() -> SslAcceptor { #[actix_rt::test] async fn test_connection_window_size() { - let srv = test_server(move || { + let srv = test_server(|| { HttpService::build() .h2(map_config( App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), From 88e074879d564bcbac5da68b06699069423536c9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:19:09 +0000 Subject: [PATCH 111/861] prepare actix-http release 3.0.0-beta.13 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ac04a66e..21a0d57a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-beta.9", default-features = false, optional = true } -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" actix-router = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.5" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c0ff18678..e61a140cb 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,7 +19,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false } -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" actix-service = "2.0.0" askama_escape = "0.10" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c670464ac..7fbc3bfb7 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7e7e9f4d4..ee8b9e99e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.13 - 2021-11-22 ### Added * `body::AnyBody::empty` for quickly creating an empty body. [#2446] * `body::AnyBody::none` for quickly creating a "none" body. [#2456] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 852c5ce23..da2a9787a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.12" +version = "3.0.0-beta.13" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] diff --git a/actix-http/README.md b/actix-http/README.md index 536d17074..d1f451e3e 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http/3.0.0-beta.12) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http/3.0.0-beta.13) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.12) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.13) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index b2f3e391c..65299864d 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -29,6 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index bc660293d..b8e554673 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" actix-http-test = "3.0.0-beta.6" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c938c6a1d..7d011ee2d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" actix-web = { version = "4.0.0-beta.11", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1dbb0ec19..f24246e3d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.12" +actix-http = "3.0.0-beta.13" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0-beta.9", features = ["connect"] } actix-utils = "3.0.0" @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.12", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.13", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.9" From 5f5bd2184ebd3ea1e150d8d10922f4c77f5f029d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:20:55 +0000 Subject: [PATCH 112/861] prepare actix-web release 4.0.0-beta.12 --- CHANGES.md | 6 +++++- Cargo.toml | 2 +- README.md | 4 ++-- actix-http/CHANGES.md | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 784500d9e..290d1dca2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] @@ -10,7 +13,8 @@ ### Removed * `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] -[#2423]: https://github.com/actix/actix-web/pull/2423 +[#2446]: https://github.com/actix/actix-web/pull/2446 +[#2448]: https://github.com/actix/actix-web/pull/2448 ## 4.0.0-beta.11 - 2021-11-15 diff --git a/Cargo.toml b/Cargo.toml index 21a0d57a2..600cb2522 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.11" +version = "4.0.0-beta.12" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index 9444f130d..cc68b5097 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.11)](https://docs.rs/actix-web/4.0.0-beta.11) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web/4.0.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.11) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.12)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ee8b9e99e..1cc7ae5fd 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -25,6 +25,7 @@ * `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 +[#2448]: https://github.com/actix/actix-web/pull/2448 [#2456]: https://github.com/actix/actix-web/pull/2456 From 99e6a9c26d65c5fe0ffee5bf2ad8bdbedd7ab74c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:41:43 +0000 Subject: [PATCH 113/861] prepare awc release 3.0.0-beta.11 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 600cb2522..15af25656 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.10", features = ["openssl"] } +awc = { version = "3.0.0-beta.11", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 7fbc3bfb7..05a907ce8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.9" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.9" -awc = { version = "3.0.0-beta.10", default-features = false } +awc = { version = "3.0.0-beta.11", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b8e554673..75b416278 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.10", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.11", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7d011ee2d..6924e09e0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,6 +29,6 @@ tokio = { version = "1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.6" -awc = { version = "3.0.0-beta.10", default-features = false } +awc = { version = "3.0.0-beta.11", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 98998fd5c..6331d30ac 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,9 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.11 - 2021-11-22 + + ## 3.0.0-beta.10 - 2021-11-15 * No significant changes from `3.0.0-beta.9`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f24246e3d..0fd0a2dfa 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.10" +version = "3.0.0-beta.11" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index 96c5ed405..7070337d0 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.10)](https://docs.rs/awc/3.0.0-beta.10) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.11)](https://docs.rs/awc/3.0.0-beta.11) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.10/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.10) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.11/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.11) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 0062d99b6f1ff9d6dd67937fafe8542d3ddf8a0e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:46:19 +0000 Subject: [PATCH 114/861] prepare actix-files release 0.6.0-beta.9 --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 8 ++++++-- actix-files/README.md | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7da775607..63d8efc3f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.9 - 2021-11-22 * Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] * Add `NamedFile::open_async`. [#2408] * Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index e61a140cb..11ec29483 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "actix-files" -version = "0.6.0-beta.8" -authors = ["Nikolay Kim "] +version = "0.6.0-beta.9" +authors = [ + "Nikolay Kim ", + "fakeshadow <24548779@qq.com>", + "Rob Ede ", +] description = "Static file serving for Actix Web" keywords = ["actix", "http", "async", "futures"] homepage = "https://actix.rs" diff --git a/actix-files/README.md b/actix-files/README.md index eac7339ab..84e556fa9 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.8)](https://docs.rs/actix-files/0.6.0-beta.8) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.9)](https://docs.rs/actix-files/0.6.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.8/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.8) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.9/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.9) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From b806b4773c726ae707fc2ae341aa406c4b7fc7e1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:46:58 +0000 Subject: [PATCH 115/861] prepare actix-http-test release 3.0.0-beta.7 --- actix-http-test/CHANGES.md | 3 +++ actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 3356f5334..37de57d42 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.7 - 2021-11-22 * Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 05a907ce8..cfc32b52f 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.6" +version = "3.0.0-beta.7" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 3eee66451..057cf6a13 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http-test/3.0.0-beta.6) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.7)](https://docs.rs/actix-http-test/3.0.0-beta.7) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.6) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.7) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index da2a9787a..1c7cd1982 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.9" -actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.9", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 75b416278..432b230dc 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" actix-http = "3.0.0-beta.13" -actix-http-test = "3.0.0-beta.6" +actix-http-test = "3.0.0-beta.7" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0fd0a2dfa..dd22f0f65 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -95,7 +95,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } actix-http = { version = "3.0.0-beta.13", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.9" actix-tls = { version = "3.0.0-beta.9", features = ["openssl", "rustls"] } From 18b8ef076564f6c6073c4ba6e62b3ee443cb66c7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:47:43 +0000 Subject: [PATCH 116/861] prepare actix-test release 0.1.0-beta.7 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 15af25656..dbe28df4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.11", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 11ec29483..f0ae00b97 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,4 +43,4 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" actix-web = "4.0.0-beta.11" -actix-test = "0.1.0-beta.6" +actix-test = "0.1.0-beta.7" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 78fd4e4ca..b739011f0 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.7 - 2021-11-22 * Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 432b230dc..afbbd0cc4 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.6" +version = "0.1.0-beta.7" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 6924e09e0..9712e0656 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.6" +actix-test = "0.1.0-beta.7" awc = { version = "3.0.0-beta.11", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a407d00fc..8497f0b23 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-rt = "2.2" actix-macros = "0.2.3" -actix-test = "0.1.0-beta.6" +actix-test = "0.1.0-beta.7" actix-utils = "3.0.0" actix-web = "4.0.0-beta.11" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index dd22f0f65..a8d0e3e33 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.9" actix-tls = { version = "3.0.0-beta.9", features = ["openssl", "rustls"] } -actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } brotli2 = "0.3.2" env_logger = "0.9" From ab5eb7c1aa0356b523afbfdbf5876f6d88697694 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 22 Nov 2021 18:48:14 +0000 Subject: [PATCH 117/861] prepare actix-multipart release 0.4.0-beta.8 --- actix-multipart/CHANGES.md | 3 +++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 97c011393..1ef4aab3f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.4.0-beta.8 - 2021-11-22 * Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] * Added `MultipartError::NoContentDisposition` variant. [#2451] * Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 65299864d..b1dea4832 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.7" +version = "0.4.0-beta.8" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 674814294..75379629d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.7)](https://docs.rs/actix-multipart/0.4.0-beta.7) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.8)](https://docs.rs/actix-multipart/0.4.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.7/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.7) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.8/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.8) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From bcbbc115aa3132c751c7290d9190a35f98ddca1a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 23 Nov 2021 15:12:55 +0000 Subject: [PATCH 118/861] fix awc changelog --- .github/workflows/ci.yml | 8 +------- awc/CHANGES.md | 1 + awc/src/builder.rs | 18 ++++++++---------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38c066d6c..d9b98a7b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,7 +159,7 @@ jobs: with: { file: cobertura.xml } rustdoc: - name: rustdoc + name: doc tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -177,12 +177,6 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - # - name: Install cargo-hack - # uses: actions-rs/cargo@v1 - # with: - # command: install - # args: cargo-hack - - name: doc tests uses: actions-rs/cargo@v1 timeout-minutes: 60 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 6331d30ac..788dce056 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,7 @@ ## 3.0.0-beta.11 - 2021-11-22 +* No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 11ececa70..fda7d93ac 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,18 +1,16 @@ -use std::convert::TryFrom; -use std::fmt; -use std::net::IpAddr; -use std::rc::Rc; -use std::time::Duration; +use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration}; use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}; use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; -use crate::client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}; -use crate::connect::DefaultConnector; -use crate::error::SendRequestError; -use crate::middleware::{NestTransform, Redirect, Transform}; -use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse}; +use crate::{ + client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, + connect::DefaultConnector, + error::SendRequestError, + middleware::{NestTransform, Redirect, Transform}, + Client, ClientConfig, ConnectRequest, ConnectResponse, +}; /// An HTTP Client builder /// From 9bdd334bb4687170351d6bfcbbde756eaeaffa0a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 23 Nov 2021 15:57:18 +0000 Subject: [PATCH 119/861] add test for duplicate dynamic segent name --- actix-router/src/resource.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index dcd655350..d5f738a05 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -1770,6 +1770,12 @@ mod tests { match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1"); } + #[test] + #[should_panic] + fn duplicate_segment_name() { + ResourceDef::new("/user/{id}/post/{id}"); + } + #[test] #[should_panic] fn invalid_dynamic_segment_delimiter() { From 3e6e9779dcc1493ca035d13d056827659b25f8ff Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 24 Nov 2021 20:16:15 +0000 Subject: [PATCH 120/861] fix big5 charset parsing --- actix-http/src/header/shared/charset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index b482f6bce..3cc0f3e23 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -88,7 +88,7 @@ impl Charset { Iso_8859_8_E => "ISO-8859-8-E", Iso_8859_8_I => "ISO-8859-8-I", Gb2312 => "GB2312", - Big5 => "big5", + Big5 => "Big5", Koi8_R => "KOI8-R", Ext(ref s) => s, } @@ -128,7 +128,7 @@ impl FromStr for Charset { "ISO-8859-8-E" => Iso_8859_8_E, "ISO-8859-8-I" => Iso_8859_8_I, "GB2312" => Gb2312, - "big5" => Big5, + "BIG5" => Big5, "KOI8-R" => Koi8_R, s => Ext(s.to_owned()), }) From 52bbbd1d7399d37ca587c67f7c130e25b61f89b1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Nov 2021 04:53:11 +0800 Subject: [PATCH 121/861] Mnior cleanup of multipart API. (#2461) --- actix-multipart/Cargo.toml | 2 +- actix-multipart/src/server.rs | 43 +++++++++++++---------------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index b1dea4832..5376ba128 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -20,7 +20,6 @@ actix-utils = "3.0.0" bytes = "1" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } httparse = "1.3" local-waker = "0.1" log = "0.4" @@ -30,5 +29,6 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" actix-http = "3.0.0-beta.13" +futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 43f9ccf5f..55ac00ff7 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -17,7 +17,6 @@ use actix_web::{ }; use bytes::{Bytes, BytesMut}; use futures_core::stream::{LocalBoxStream, Stream}; -use futures_util::stream::StreamExt as _; use local_waker::LocalWaker; use crate::error::MultipartError; @@ -67,7 +66,7 @@ impl Multipart { /// Create multipart instance for boundary. pub fn new(headers: &HeaderMap, stream: S) -> Multipart where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, { match Self::boundary(headers) { Ok(boundary) => Multipart::from_boundary(boundary, stream), @@ -77,36 +76,29 @@ impl Multipart { /// Extract boundary info from headers. pub(crate) fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - if let Some(boundary) = ct.get_param(mime::BOUNDARY) { - Ok(boundary.as_str().to_owned()) - } else { - Err(MultipartError::Boundary) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::NoContentType) - } + headers + .get(&header::CONTENT_TYPE) + .ok_or(MultipartError::NoContentType)? + .to_str() + .ok() + .and_then(|content_type| content_type.parse::().ok()) + .ok_or(MultipartError::ParseContentType)? + .get_param(mime::BOUNDARY) + .map(|boundary| boundary.as_str().to_owned()) + .ok_or(MultipartError::Boundary) } /// Create multipart instance for given boundary and stream pub(crate) fn from_boundary(boundary: String, stream: S) -> Multipart where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, { Multipart { error: None, safety: Safety::new(), inner: Some(Rc::new(RefCell::new(InnerMultipart { boundary, - payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))), + payload: PayloadRef::new(PayloadBuffer::new(stream)), state: InnerState::FirstBoundary, item: InnerMultipartItem::None, }))), @@ -690,10 +682,7 @@ impl PayloadRef { } } - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option> - where - 'a: 'b, - { + fn get_mut(&self, s: &Safety) -> Option> { if s.current() { Some(self.payload.borrow_mut()) } else { @@ -779,7 +768,7 @@ impl PayloadBuffer { PayloadBuffer { eof: false, buf: BytesMut::new(), - stream: stream.boxed_local(), + stream: Box::pin(stream), } } @@ -860,7 +849,7 @@ mod tests { use actix_web::test::TestRequest; use actix_web::FromRequest; use bytes::Bytes; - use futures_util::future::lazy; + use futures_util::{future::lazy, StreamExt}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; From 89c6d626569af3ca1deb22be64f111d121389476 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 25 Nov 2021 08:10:53 +0800 Subject: [PATCH 122/861] clean up multipart and field stream trait impl (#2462) --- actix-multipart/src/server.rs | 75 +++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 55ac00ff7..c9642cfad 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -32,7 +32,7 @@ const MAX_HEADERS: usize = 32; pub struct Multipart { safety: Safety, error: Option, - inner: Option>>, + inner: Option, } enum InnerMultipartItem { @@ -96,12 +96,12 @@ impl Multipart { Multipart { error: None, safety: Safety::new(), - inner: Some(Rc::new(RefCell::new(InnerMultipart { + inner: Some(InnerMultipart { boundary, payload: PayloadRef::new(PayloadBuffer::new(stream)), state: InnerState::FirstBoundary, item: InnerMultipartItem::None, - }))), + }), } } @@ -118,20 +118,27 @@ impl Multipart { impl Stream for Multipart { type Item = Result; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if let Some(err) = self.error.take() { - Poll::Ready(Some(Err(err))) - } else if self.safety.current() { - let this = self.get_mut(); - let mut inner = this.inner.as_mut().unwrap().borrow_mut(); - if let Some(mut payload) = inner.payload.get_mut(&this.safety) { - payload.poll_stream(cx)?; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + match this.inner.as_mut() { + Some(inner) => { + if let Some(mut buffer) = inner.payload.get_mut(&this.safety) { + // check safety and poll read payload to buffer. + buffer.poll_stream(cx)?; + } else if !this.safety.is_clean() { + // safety violation + return Poll::Ready(Some(Err(MultipartError::NotConsumed))); + } else { + return Poll::Pending; + } + + inner.poll(&this.safety, cx) } - inner.poll(&this.safety, cx) - } else if !self.safety.is_clean() { - Poll::Ready(Some(Err(MultipartError::NotConsumed))) - } else { - Poll::Pending + None => Poll::Ready(Some(Err(this + .error + .take() + .expect("Multipart polled after finish")))), } } } @@ -152,17 +159,15 @@ impl InnerMultipart { Ok(httparse::Status::Complete((_, hdrs))) => { // convert headers let mut headers = HeaderMap::with_capacity(hdrs.len()); + for h in hdrs { - if let Ok(name) = HeaderName::try_from(h.name) { - if let Ok(value) = HeaderValue::try_from(h.value) { - headers.append(name, value); - } else { - return Err(ParseError::Header.into()); - } - } else { - return Err(ParseError::Header.into()); - } + let name = + HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?; + let value = HeaderValue::try_from(h.value) + .map_err(|_| ParseError::Header)?; + headers.append(name, value); } + Ok(Some(headers)) } Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), @@ -458,17 +463,19 @@ impl Stream for Field { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.safety.current() { - let mut inner = self.inner.borrow_mut(); - if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) { - payload.poll_stream(cx)?; - } - inner.poll(&self.safety) - } else if !self.safety.is_clean() { - Poll::Ready(Some(Err(MultipartError::NotConsumed))) + let this = self.get_mut(); + let mut inner = this.inner.borrow_mut(); + if let Some(mut buffer) = inner.payload.as_ref().unwrap().get_mut(&this.safety) { + // check safety and poll read payload to buffer. + buffer.poll_stream(cx)?; + } else if !this.safety.is_clean() { + // safety violation + return Poll::Ready(Some(Err(MultipartError::NotConsumed))); } else { - Poll::Pending + return Poll::Pending; } + + inner.poll(&this.safety) } } From 39243095b5a3399b5a39913fb2d1b0f494c3a4ba Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 28 Nov 2021 19:23:29 +0000 Subject: [PATCH 123/861] guarantee ordering of header map get_all (#2467) --- actix-http/CHANGES.md | 5 ++ actix-http/src/header/map.rs | 102 +++++++++++++++++++++++++++++++-- actix-http/src/header/mod.rs | 16 +++--- actix-http/src/header/utils.rs | 1 + actix-http/src/ws/proto.rs | 2 +- 5 files changed, 111 insertions(+), 15 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1cc7ae5fd..d3aa7fc0e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +* Expose `header::{GetAll, Removed}` iterators. [#2467] + +[#2467]: https://github.com/actix/actix-web/pull/2467 ## 3.0.0-beta.13 - 2021-11-22 diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index a8fd9715b..c7e4921a8 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -288,7 +288,7 @@ impl HeaderMap { /// Returns an iterator over all values associated with a header name. /// /// The returned iterator does not incur any allocations and will yield no items if there are no - /// values associated with the key. Iteration order is **not** guaranteed to be the same as + /// values associated with the key. Iteration order is guaranteed to be the same as /// insertion order. /// /// # Examples @@ -355,6 +355,19 @@ impl HeaderMap { /// /// assert_eq!(map.len(), 1); /// ``` + /// + /// A convenience method is provided on the returned iterator to check if the insertion replaced + /// any values. + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(removed.is_empty()); + /// + /// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/html")); + /// assert!(!removed.is_empty()); + /// ``` pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed { let value = self.inner.insert(key, Value::one(val)); Removed::new(value) @@ -393,6 +406,9 @@ impl HeaderMap { /// Removes all headers for a particular header name from the map. /// + /// Providing an invalid header names (as a string argument) will have no effect and return + /// without error. + /// /// # Examples /// ``` /// # use actix_http::http::{header, HeaderMap, HeaderValue}; @@ -409,6 +425,21 @@ impl HeaderMap { /// assert!(removed.next().is_none()); /// /// assert!(map.is_empty()); + /// ``` + /// + /// A convenience method is provided on the returned iterator to check if the `remove` call + /// actually removed any values. + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let removed = map.remove("accept"); + /// assert!(removed.is_empty()); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/html")); + /// let removed = map.remove("accept"); + /// assert!(!removed.is_empty()); + /// ``` pub fn remove(&mut self, key: impl AsHeaderName) -> Removed { let value = match key.try_as_name(super::as_name::Seal) { Ok(Cow::Borrowed(name)) => self.inner.remove(name), @@ -571,7 +602,7 @@ impl<'a> IntoIterator for &'a HeaderMap { } } -/// Iterator for all values with the same header name. +/// Iterator for references of [`HeaderValue`]s with the same associated [`HeaderName`]. /// /// See [`HeaderMap::get_all`]. #[derive(Debug)] @@ -613,18 +644,32 @@ impl<'a> Iterator for GetAll<'a> { } } -/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from methods -/// on [`HeaderMap`] that remove or replace items. +/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from +/// methods that remove or replace items. +/// +/// See [`HeaderMap::insert`] and [`HeaderMap::remove`]. #[derive(Debug)] pub struct Removed { inner: Option>, } -impl<'a> Removed { +impl Removed { fn new(value: Option) -> Self { let inner = value.map(|value| value.inner.into_iter()); Self { inner } } + + /// Returns true if iterator contains no elements, without consuming it. + /// + /// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate + /// wether any items were actually replaced or removed, respectively. + pub fn is_empty(&self) -> bool { + match self.inner { + // size hint lower bound of smallvec is the correct length + Some(ref iter) => iter.size_hint().0 == 0, + None => true, + } + } } impl Iterator for Removed { @@ -945,6 +990,53 @@ mod tests { assert_eq!(vals.next(), removed.next().as_ref()); } + #[test] + fn get_all_iteration_order_matches_insertion_order() { + let mut map = HeaderMap::new(); + + let mut vals = map.get_all(header::COOKIE); + assert!(vals.next().is_none()); + + map.append(header::COOKIE, HeaderValue::from_static("1")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"1"); + assert!(vals.next().is_none()); + + map.append(header::COOKIE, HeaderValue::from_static("2")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"1"); + assert_eq!(vals.next().unwrap().as_bytes(), b"2"); + assert!(vals.next().is_none()); + + map.append(header::COOKIE, HeaderValue::from_static("3")); + map.append(header::COOKIE, HeaderValue::from_static("4")); + map.append(header::COOKIE, HeaderValue::from_static("5")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"1"); + assert_eq!(vals.next().unwrap().as_bytes(), b"2"); + assert_eq!(vals.next().unwrap().as_bytes(), b"3"); + assert_eq!(vals.next().unwrap().as_bytes(), b"4"); + assert_eq!(vals.next().unwrap().as_bytes(), b"5"); + assert!(vals.next().is_none()); + + let _ = map.insert(header::COOKIE, HeaderValue::from_static("6")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"6"); + assert!(vals.next().is_none()); + + let _ = map.insert(header::COOKIE, HeaderValue::from_static("7")); + let _ = map.insert(header::COOKIE, HeaderValue::from_static("8")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"8"); + assert!(vals.next().is_none()); + + map.append(header::COOKIE, HeaderValue::from_static("9")); + let mut vals = map.get_all(header::COOKIE); + assert_eq!(vals.next().unwrap().as_bytes(), b"8"); + assert_eq!(vals.next().unwrap().as_bytes(), b"9"); + assert!(vals.next().is_none()); + } + fn owned_pair<'a>( (name, val): (&'a HeaderName, &'a HeaderValue), ) -> (HeaderName, HeaderValue) { diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 18494f555..a9483a9ff 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -29,16 +29,14 @@ pub use http::header::{ X_FRAME_OPTIONS, X_XSS_PROTECTION, }; -use crate::error::ParseError; -use crate::HttpMessage; +use crate::{error::ParseError, HttpMessage}; mod as_name; mod into_pair; mod into_value; -mod utils; - pub(crate) mod map; mod shared; +mod utils; #[doc(hidden)] pub use self::shared::*; @@ -46,10 +44,10 @@ pub use self::shared::*; pub use self::as_name::AsHeaderName; 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::*; +pub use self::map::{GetAll, HeaderMap, Removed}; +pub use self::utils::{ + fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, +}; /// A trait for any object that already represents a valid header field and value. pub trait Header: IntoHeaderValue { @@ -68,7 +66,7 @@ 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. +/// . pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index 5e9652380..cf8636e9d 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -56,6 +56,7 @@ where /// Percent encode a sequence of bytes with a character set defined in /// +#[inline] 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/ws/proto.rs b/actix-http/src/ws/proto.rs index fdcde5eac..8ec04a5c3 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -220,7 +220,7 @@ impl> From<(CloseCode, T)> for CloseReason { } } -/// The WebSocket GUID as stated in the spec. See https://tools.ietf.org/html/rfc6455#section-1.3. +/// The WebSocket GUID as stated in the spec. See . static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; /// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec. From cf5438853471e27cf8acf8806f2ed4e0f81c2caf Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 29 Nov 2021 09:23:27 +0800 Subject: [PATCH 124/861] re-work from request macro. (#2469) --- Cargo.toml | 1 + src/extract.rs | 231 +++++++++++++++++++++++++++---------------------- 2 files changed, 130 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dbe28df4a..96c8fef7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ log = "0.4" mime = "0.3" paste = "1" pin-project = "1.0.0" +pin-project-lite = "0.2.7" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/src/extract.rs b/src/extract.rs index 29fd0d05e..bb2dabb9f 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -10,6 +10,7 @@ use std::{ use actix_http::http::{Method, Uri}; use actix_utils::future::{ok, Ready}; use futures_core::ready; +use pin_project_lite::pin_project; use crate::{dev::Payload, Error, HttpRequest}; @@ -139,10 +140,11 @@ where } } -#[pin_project::pin_project] -pub struct FromRequestOptFuture { - #[pin] - fut: Fut, +pin_project! { + pub struct FromRequestOptFuture { + #[pin] + fut: Fut, + } } impl Future for FromRequestOptFuture @@ -226,10 +228,11 @@ where } } -#[pin_project::pin_project] -pub struct FromRequestResFuture { - #[pin] - fut: Fut, +pin_project! { + pub struct FromRequestResFuture { + #[pin] + fut: Fut, + } } impl Future for FromRequestResFuture @@ -297,102 +300,104 @@ impl FromRequest for () { } } -macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { - - // This module is a trick to get around the inability of - // `macro_rules!` macros to make new idents. We want to make - // a new `FutWrapper` struct for each distinct invocation of - // this macro. Ideally, we would name it something like - // `FutWrapper_$fut_type`, but this can't be done in a macro_rules - // macro. - // - // Instead, we put everything in a module named `$fut_type`, thus allowing - // us to use the name `FutWrapper` without worrying about conflicts. - // This macro only exists to generate trait impls for tuples - these - // are inherently global, so users don't have to care about this - // weird trick. - #[allow(non_snake_case)] - mod $fut_type { - - // Bring everything into scope, so we don't need - // redundant imports - use super::*; - - /// A helper struct to allow us to pin-project through - /// to individual fields - #[pin_project::pin_project] - struct FutWrapper<$($T: FromRequest),+>($(#[pin] $T::Future),+); - - /// FromRequest implementation for tuple - #[doc(hidden)] - #[allow(unused_parens)] - impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) - { - type Error = Error; - type Future = $fut_type<$($T),+>; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - $fut_type { - items: <($(Option<$T>,)+)>::default(), - futs: FutWrapper($($T::from_request(req, payload),)+), - } - } - } - - #[doc(hidden)] - #[pin_project::pin_project] - pub struct $fut_type<$($T: FromRequest),+> { - items: ($(Option<$T>,)+), - #[pin] - futs: FutWrapper<$($T,)+>, - } - - impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> - { - type Output = Result<($($T,)+), Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.project(); - - let mut ready = true; - $( - if this.items.$n.is_none() { - match this.futs.as_mut().project().$n.poll(cx) { - Poll::Ready(Ok(item)) => { - this.items.$n = Some(item); - } - Poll::Pending => ready = false, - Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), - } - } - )+ - - if ready { - Poll::Ready(Ok( - ($(this.items.$n.take().unwrap(),)+) - )) - } else { - Poll::Pending - } - } - } - } -}); - -#[rustfmt::skip] -mod m { +#[doc(hidden)] +#[allow(non_snake_case)] +mod tuple_from_req { use super::*; - tuple_from_req!(TupleFromRequest1, (0, A)); - tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); - tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); - tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); - tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); - tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + macro_rules! tuple_from_req { + ($fut: ident; $($T: ident),*) => { + /// FromRequest implementation for tuple + #[allow(unused_parens)] + impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) + { + type Error = Error; + type Future = $fut<$($T),+>; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + $fut { + $( + $T: ExtractFuture::Future { + fut: $T::from_request(req, payload) + }, + )+ + } + } + } + + pin_project! { + pub struct $fut<$($T: FromRequest),+> { + $( + #[pin] + $T: ExtractFuture<$T::Future, $T>, + )+ + } + } + + impl<$($T: FromRequest),+> Future for $fut<$($T),+> + { + type Output = Result<($($T,)+), Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + let mut ready = true; + $( + match this.$T.as_mut().project() { + ExtractProj::Future { fut } => match fut.poll(cx) { + Poll::Ready(Ok(output)) => { + let _ = this.$T.as_mut().project_replace(ExtractFuture::Done { output }); + }, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), + Poll::Pending => ready = false, + }, + ExtractProj::Done { .. } => {}, + ExtractProj::Empty => unreachable!("FromRequest polled after finished"), + } + )+ + + if ready { + Poll::Ready(Ok( + ($( + match this.$T.project_replace(ExtractFuture::Empty) { + ExtractReplaceProj::Done { output } => output, + _ => unreachable!("FromRequest polled after finished"), + }, + )+) + )) + } else { + Poll::Pending + } + } + } + }; + } + + pin_project! { + #[project = ExtractProj] + #[project_replace = ExtractReplaceProj] + enum ExtractFuture { + Future { + #[pin] + fut: Fut + }, + Done { + output: Res, + }, + Empty + } + } + + tuple_from_req! { TupleFromRequest1; A } + tuple_from_req! { TupleFromRequest2; A, B } + tuple_from_req! { TupleFromRequest3; A, B, C } + tuple_from_req! { TupleFromRequest4; A, B, C, D } + tuple_from_req! { TupleFromRequest5; A, B, C, D, E } + tuple_from_req! { TupleFromRequest6; A, B, C, D, E, F } + tuple_from_req! { TupleFromRequest7; A, B, C, D, E, F, G } + tuple_from_req! { TupleFromRequest8; A, B, C, D, E, F, G, H } + tuple_from_req! { TupleFromRequest9; A, B, C, D, E, F, G, H, I } + tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J } } #[cfg(test)] @@ -494,4 +499,26 @@ mod tests { let method = Method::extract(&req).await.unwrap(); assert_eq!(method, Method::GET); } + + #[actix_rt::test] + async fn test_concurrent() { + let (req, mut pl) = TestRequest::default() + .uri("/foo/bar") + .method(Method::GET) + .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 (method, uri, form) = <(Method, Uri, Form)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(method, Method::GET); + assert_eq!(uri.path(), "/foo/bar"); + assert_eq!( + form, + Form(Info { + hello: "world".into() + }) + ); + } } From 654dc64a0925ae47931bcb2d5ba3b9d7bd29cf46 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 29 Nov 2021 05:00:24 +0300 Subject: [PATCH 125/861] don't hang after dropping mutipart (#2463) --- actix-multipart/CHANGES.md | 2 ++ actix-multipart/src/server.rs | 53 ++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 1ef4aab3f..deb999878 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -10,8 +10,10 @@ * Added `Field::name` method for getting the field name. [#2451] * `MultipartError` now marks variants with inner errors as the source. [#2451] * `MultipartError` is now marked as non-exhaustive. [#2451] +* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2451]: https://github.com/actix/actix-web/pull/2451 +[#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.7 - 2021-10-20 diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c9642cfad..319e79863 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -706,8 +706,11 @@ impl Clone for PayloadRef { } } -/// Counter. It tracks of number of clones of payloads and give access to payload only to top most -/// task panics if Safety get destroyed and it not top most task. +/// Counter. It tracks of number of clones of payloads and give access to payload only to top most. +/// * When dropped, parent task is awakened. This is to support the case where Field is +/// dropped in a separate task than Multipart. +/// * Assumes that parent owners don't move to different tasks; only the top-most is allowed to. +/// * If dropped and is not top most owner, is_clean flag is set to false. #[derive(Debug)] struct Safety { task: LocalWaker, @@ -750,9 +753,9 @@ impl Safety { impl Drop for Safety { fn drop(&mut self) { - // parent task is dead if Rc::strong_count(&self.payload) != self.level { - self.clean.set(true); + // Multipart dropped leaving a Field + self.clean.set(false); } self.task.wake(); @@ -853,10 +856,12 @@ mod tests { use actix_http::h1::Payload; use actix_web::http::header::{DispositionParam, DispositionType}; + use actix_web::rt; use actix_web::test::TestRequest; use actix_web::FromRequest; use bytes::Bytes; use futures_util::{future::lazy, StreamExt}; + use std::time::Duration; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -1286,4 +1291,44 @@ mod tests { MultipartError::NoContentDisposition, )); } + + #[actix_rt::test] + async fn test_drop_multipart_dont_hang() { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); + sender.send(Ok(bytes)).unwrap(); + drop(sender); // eof + + let mut multipart = Multipart::new(&headers, payload); + let mut field = multipart.next().await.unwrap().unwrap(); + + drop(multipart); + + // should fail immediately + match field.next().await { + Some(Err(MultipartError::NotConsumed)) => {} + _ => panic!(), + }; + } + + #[actix_rt::test] + async fn test_drop_field_awaken_multipart() { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); + sender.send(Ok(bytes)).unwrap(); + drop(sender); // eof + + let mut multipart = Multipart::new(&headers, payload); + let mut field = multipart.next().await.unwrap().unwrap(); + + let task = rt::spawn(async move { + rt::time::sleep(Duration::from_secs(1)).await; + assert_eq!(field.next().await.unwrap().unwrap(), "test"); + drop(field); + }); + + // dropping field should awaken current task + let _ = multipart.next().await.unwrap().unwrap(); + task.await.unwrap(); + } } From fc4cdf81ebd4137c6df85f6ff9b29aae79a343d9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 29 Nov 2021 02:22:47 +0000 Subject: [PATCH 126/861] expose `header::map` module (#2470) --- actix-http/CHANGES.md | 4 +- actix-http/src/header/as_name.rs | 9 ++-- actix-http/src/header/into_pair.rs | 9 +++- actix-http/src/header/into_value.rs | 6 ++- actix-http/src/header/map.rs | 54 ++++++++++++++++--- actix-http/src/header/mod.rs | 6 +-- .../src/header/shared/content_encoding.rs | 11 ++-- actix-http/src/header/utils.rs | 2 + src/response/response.rs | 2 +- 9 files changed, 79 insertions(+), 24 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d3aa7fc0e..9e004e13b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,9 +3,11 @@ ## Unreleased - 2021-xx-xx ### Changed * Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -* Expose `header::{GetAll, Removed}` iterators. [#2467] +* Expose `header::map` module. [#2467] +* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] [#2467]: https://github.com/actix/actix-web/pull/2467 +[#2470]: https://github.com/actix/actix-web/pull/2470 ## 3.0.0-beta.13 - 2021-11-22 diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index 5ce321566..04d32c41d 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -1,11 +1,12 @@ -//! Helper trait for types that can be effectively borrowed as a [HeaderValue]. -//! -//! [HeaderValue]: crate::http::HeaderValue +//! Sealed [`AsHeaderName`] trait and implementations. -use std::{borrow::Cow, str::FromStr}; +use std::{borrow::Cow, str::FromStr as _}; use http::header::{HeaderName, InvalidHeaderName}; +/// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`]. +/// +/// [`HeaderValue`]: crate::http::HeaderValue pub trait AsHeaderName: Sealed {} pub struct Seal; diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs index d0d6e7324..472700548 100644 --- a/actix-http/src/header/into_pair.rs +++ b/actix-http/src/header/into_pair.rs @@ -1,4 +1,6 @@ -use std::convert::TryFrom; +//! [`IntoHeaderPair`] trait and implementations. + +use std::convert::TryFrom as _; use http::{ header::{HeaderName, InvalidHeaderName, InvalidHeaderValue}, @@ -7,7 +9,10 @@ use http::{ use super::{Header, IntoHeaderValue}; -/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s. +/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for +/// insertion into a [`HeaderMap`]. +/// +/// [`HeaderMap`]: crate::http::HeaderMap pub trait IntoHeaderPair: Sized { type Error: Into; diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs index 4ba58e726..bad05db64 100644 --- a/actix-http/src/header/into_value.rs +++ b/actix-http/src/header/into_value.rs @@ -1,10 +1,12 @@ -use std::convert::TryFrom; +//! [`IntoHeaderValue`] trait and implementations. + +use std::convert::TryFrom as _; 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` +/// An interface for types that can be converted into a [`HeaderValue`]. pub trait IntoHeaderValue: Sized { /// The type returned in the event of a conversion error. type Error: Into; diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index c7e4921a8..dd852b021 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,6 +1,6 @@ //! A multi-value [`HeaderMap`] and its iterators. -use std::{borrow::Cow, collections::hash_map, ops}; +use std::{borrow::Cow, collections::hash_map, iter, ops}; use ahash::AHashMap; use http::header::{HeaderName, HeaderValue}; @@ -581,7 +581,8 @@ impl HeaderMap { } } -/// Note that this implementation will clone a [HeaderName] for each value. +/// Note that this implementation will clone a [HeaderName] for each value. Consider using +/// [`drain`](Self::drain) to control header name cloning. impl IntoIterator for HeaderMap { type Item = (HeaderName, HeaderValue); type IntoIter = IntoIter; @@ -602,7 +603,7 @@ impl<'a> IntoIterator for &'a HeaderMap { } } -/// Iterator for references of [`HeaderValue`]s with the same associated [`HeaderName`]. +/// Iterator over borrowed values with the same associated name. /// /// See [`HeaderMap::get_all`]. #[derive(Debug)] @@ -644,10 +645,14 @@ impl<'a> Iterator for GetAll<'a> { } } -/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from -/// methods that remove or replace items. +impl ExactSizeIterator for GetAll<'_> {} + +impl iter::FusedIterator for GetAll<'_> {} + +/// Iterator over removed, owned values with the same associated name. /// -/// See [`HeaderMap::insert`] and [`HeaderMap::remove`]. +/// Returned from methods that remove or replace items. See [`HeaderMap::insert`] +/// and [`HeaderMap::remove`]. #[derive(Debug)] pub struct Removed { inner: Option>, @@ -689,7 +694,11 @@ impl Iterator for Removed { } } -/// Iterator over all [`HeaderName`]s in the map. +impl ExactSizeIterator for Removed {} + +impl iter::FusedIterator for Removed {} + +/// Iterator over all names in the map. #[derive(Debug)] pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); @@ -707,6 +716,11 @@ impl<'a> Iterator for Keys<'a> { } } +impl ExactSizeIterator for Keys<'_> {} + +impl iter::FusedIterator for Keys<'_> {} + +/// Iterator over borrowed name-value pairs. #[derive(Debug)] pub struct Iter<'a> { inner: hash_map::Iter<'a, HeaderName, Value>, @@ -758,6 +772,10 @@ impl<'a> Iterator for Iter<'a> { } } +impl ExactSizeIterator for Iter<'_> {} + +impl iter::FusedIterator for Iter<'_> {} + /// Iterator over drained name-value pairs. /// /// Iterator items are `(Option, HeaderValue)` to avoid cloning. @@ -809,6 +827,10 @@ impl<'a> Iterator for Drain<'a> { } } +impl ExactSizeIterator for Drain<'_> {} + +impl iter::FusedIterator for Drain<'_> {} + /// Iterator over owned name-value pairs. /// /// Implementation necessarily clones header names for each value. @@ -859,12 +881,27 @@ impl Iterator for IntoIter { } } +impl ExactSizeIterator for IntoIter {} + +impl iter::FusedIterator for IntoIter {} + #[cfg(test)] mod tests { + use std::iter::FusedIterator; + use http::header; + use static_assertions::assert_impl_all; use super::*; + assert_impl_all!(HeaderMap: IntoIterator); + assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(GetAll<'_>: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(Drain<'_>: Iterator, ExactSizeIterator, FusedIterator); + #[test] fn create() { let map = HeaderMap::new(); @@ -1035,6 +1072,9 @@ mod tests { assert_eq!(vals.next().unwrap().as_bytes(), b"8"); assert_eq!(vals.next().unwrap().as_bytes(), b"9"); assert!(vals.next().is_none()); + + // check for fused-ness + assert!(vals.next().is_none()); } fn owned_pair<'a>( diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index a9483a9ff..125f7ef16 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -34,7 +34,7 @@ use crate::{error::ParseError, HttpMessage}; mod as_name; mod into_pair; mod into_value; -pub(crate) mod map; +pub mod map; mod shared; mod utils; @@ -44,12 +44,12 @@ pub use self::shared::*; pub use self::as_name::AsHeaderName; pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; -pub use self::map::{GetAll, HeaderMap, Removed}; +pub use self::map::HeaderMap; pub use self::utils::{ fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, }; -/// A trait for any object that already represents a valid header field and value. +/// An interface for types that already represent a valid header. pub trait Header: IntoHeaderValue { /// Returns the name of the header field fn name() -> HeaderName; diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index 1af109c06..073d90dce 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -9,14 +9,17 @@ use crate::{ HttpMessage, }; -/// Error return when a content encoding is unknown. -/// -/// Example: 'compress' +/// Error returned when a content encoding is unknown. #[derive(Debug, Display, Error)] #[display(fmt = "unsupported content encoding")] pub struct ContentEncodingParseError; /// Represents a supported content encoding. +/// +/// Includes a commonly-used subset of media types appropriate for use as HTTP content encodings. +/// See [IANA HTTP Content Coding Registry]. +/// +/// [IANA HTTP Content Coding Registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml #[derive(Debug, Clone, Copy, PartialEq)] #[non_exhaustive] pub enum ContentEncoding { @@ -32,7 +35,7 @@ pub enum ContentEncoding { /// Gzip algorithm. Gzip, - // Zstd algorithm. + /// Zstd algorithm. Zstd, /// Indicates the identity function (i.e. no compression, nor modification). diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index cf8636e9d..c40d1cc90 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -1,3 +1,5 @@ +//! Header parsing utilities. + use std::{fmt, str::FromStr}; use super::HeaderValue; diff --git a/src/response/response.rs b/src/response/response.rs index 6475a3816..23562ab0e 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -296,7 +296,7 @@ impl Future for HttpResponse { #[cfg(feature = "cookies")] pub struct CookieIter<'a> { - iter: header::GetAll<'a>, + iter: header::map::GetAll<'a>, } #[cfg(feature = "cookies")] From fa82b698b765fcec9d0852896cede905d8d753c8 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 30 Nov 2021 19:16:53 +0800 Subject: [PATCH 127/861] remove pin-project from actix-web. (#2471) --- Cargo.toml | 1 - src/middleware/compat.rs | 10 +++-- src/middleware/compress.rs | 21 ++++----- src/middleware/default_headers.rs | 14 +++--- src/middleware/err_handlers.rs | 29 ++++++------ src/middleware/logger.rs | 73 +++++++++++++++---------------- src/types/either.rs | 62 ++++++++++++++------------ 7 files changed, 110 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96c8fef7b..c65b0732d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,6 @@ once_cell = "1.5" log = "0.4" mime = "0.3" paste = "1" -pin-project = "1.0.0" pin-project-lite = "0.2.7" regex = "1.4" serde = { version = "1.0", features = ["derive"] } diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 752e90f94..a75335981 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -10,6 +10,7 @@ use std::{ use actix_http::body::{AnyBody, MessageBody}; use actix_service::{Service, Transform}; use futures_core::{future::LocalBoxFuture, ready}; +use pin_project_lite::pin_project; use crate::{error::Error, service::ServiceResponse}; @@ -89,10 +90,11 @@ where } } -#[pin_project::pin_project] -pub struct CompatMiddlewareFuture { - #[pin] - fut: Fut, +pin_project! { + pub struct CompatMiddlewareFuture { + #[pin] + fut: Fut, + } } impl Future for CompatMiddlewareFuture diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d8878a82a..3b99fd6b3 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -20,7 +20,7 @@ use actix_utils::future::{ok, Either, Ready}; use bytes::Bytes; use futures_core::ready; use once_cell::sync::Lazy; -use pin_project::pin_project; +use pin_project_lite::pin_project; use crate::{ dev::BodyEncoding, @@ -162,15 +162,16 @@ where } } -#[pin_project] -pub struct CompressResponse -where - S: Service, -{ - #[pin] - fut: S::Future, - encoding: ContentEncoding, - _phantom: PhantomData, +pin_project! { + pub struct CompressResponse + where + S: Service, + { + #[pin] + fut: S::Future, + encoding: ContentEncoding, + _phantom: PhantomData, + } } impl Future for CompressResponse diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index d8a947aab..426810247 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -11,6 +11,7 @@ use std::{ use actix_utils::future::{ready, Ready}; use futures_core::ready; +use pin_project_lite::pin_project; use crate::{ dev::{Service, Transform}, @@ -153,12 +154,13 @@ where } } -#[pin_project::pin_project] -pub struct DefaultHeaderFuture, B> { - #[pin] - fut: S::Future, - inner: Rc, - _body: PhantomData, +pin_project! { + pub struct DefaultHeaderFuture, B> { + #[pin] + fut: S::Future, + inner: Rc, + _body: PhantomData, + } } impl Future for DefaultHeaderFuture diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 75cc819bc..1a834c1e8 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -10,6 +10,7 @@ use std::{ use actix_service::{Service, Transform}; use ahash::AHashMap; use futures_core::{future::LocalBoxFuture, ready}; +use pin_project_lite::pin_project; use crate::{ dev::{ServiceRequest, ServiceResponse}, @@ -130,19 +131,21 @@ where } } -#[pin_project::pin_project(project = ErrorHandlersProj)] -pub enum ErrorHandlersFuture -where - Fut: Future, -{ - ServiceFuture { - #[pin] - fut: Fut, - handlers: Handlers, - }, - HandlerFuture { - fut: LocalBoxFuture<'static, Fut::Output>, - }, +pin_project! { + #[project = ErrorHandlersProj] + pub enum ErrorHandlersFuture + where + Fut: Future, + { + ServiceFuture { + #[pin] + fut: Fut, + handlers: Handlers, + }, + HandlerFuture { + fut: LocalBoxFuture<'static, Fut::Output>, + }, + } } impl Future for ErrorHandlersFuture diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index b4d100b3e..6ab16a4eb 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -13,10 +13,11 @@ use std::{ }; use actix_service::{Service, Transform}; -use actix_utils::future::{ok, Ready}; +use actix_utils::future::{ready, Ready}; use bytes::Bytes; use futures_core::ready; use log::{debug, warn}; +use pin_project_lite::pin_project; use regex::{Regex, RegexSet}; use time::{format_description::well_known::Rfc3339, OffsetDateTime}; @@ -180,8 +181,8 @@ where { type Response = ServiceResponse>; type Error = Error; - type InitError = (); type Transform = LoggerMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -195,10 +196,10 @@ where } } - ok(LoggerMiddleware { + ready(Ok(LoggerMiddleware { service, inner: self.0.clone(), - }) + })) } } @@ -246,17 +247,18 @@ where } } -#[pin_project::pin_project] -pub struct LoggerResponse -where - B: MessageBody, - S: Service, -{ - #[pin] - fut: S::Future, - time: OffsetDateTime, - format: Option, - _phantom: PhantomData, +pin_project! { + pub struct LoggerResponse + where + B: MessageBody, + S: Service, + { + #[pin] + fut: S::Future, + time: OffsetDateTime, + format: Option, + _phantom: PhantomData, + } } impl Future for LoggerResponse @@ -296,28 +298,25 @@ where } } -use pin_project::{pin_project, pinned_drop}; - -#[pin_project(PinnedDrop)] -pub struct StreamLog { - #[pin] - body: B, - format: Option, - size: usize, - time: OffsetDateTime, -} - -#[pinned_drop] -impl PinnedDrop for StreamLog { - fn drop(self: Pin<&mut Self>) { - if let Some(ref format) = self.format { - let render = |fmt: &mut fmt::Formatter<'_>| { - for unit in &format.0 { - unit.render(fmt, self.size, self.time)?; - } - Ok(()) - }; - log::info!("{}", FormatDisplay(&render)); +pin_project! { + pub struct StreamLog { + #[pin] + body: B, + format: Option, + size: usize, + time: OffsetDateTime, + } + impl PinnedDrop for StreamLog { + fn drop(this: Pin<&mut Self>) { + if let Some(ref format) = this.format { + let render = |fmt: &mut fmt::Formatter<'_>| { + for unit in &format.0 { + unit.render(fmt, this.size, this.time)?; + } + Ok(()) + }; + log::info!("{}", FormatDisplay(&render)); + } } } } diff --git a/src/types/either.rs b/src/types/either.rs index 5700b63c7..0a8a90133 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -9,6 +9,7 @@ use std::{ use bytes::Bytes; use futures_core::ready; +use pin_project_lite::pin_project; use crate::{ dev, @@ -198,37 +199,40 @@ where } } -#[pin_project::pin_project] -pub struct EitherExtractFut -where - R: FromRequest, - L: FromRequest, -{ - req: HttpRequest, - #[pin] - state: EitherExtractState, +pin_project! { + pub struct EitherExtractFut + where + R: FromRequest, + L: FromRequest, + { + req: HttpRequest, + #[pin] + state: EitherExtractState, + } } -#[pin_project::pin_project(project = EitherExtractProj)] -pub enum EitherExtractState -where - L: FromRequest, - R: FromRequest, -{ - Bytes { - #[pin] - bytes: ::Future, - }, - Left { - #[pin] - left: L::Future, - fallback: Bytes, - }, - Right { - #[pin] - right: R::Future, - left_err: Option, - }, +pin_project! { + #[project = EitherExtractProj] + pub enum EitherExtractState + where + L: FromRequest, + R: FromRequest, + { + Bytes { + #[pin] + bytes: ::Future, + }, + Left { + #[pin] + left: L::Future, + fallback: Bytes, + }, + Right { + #[pin] + right: R::Future, + left_err: Option, + }, + } } impl Future for EitherExtractFut From a978b417f38716859343fb128a91ab6e8aa8d32c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 30 Nov 2021 13:11:41 +0000 Subject: [PATCH 128/861] use actix ready future in remaining return types --- actix-files/Cargo.toml | 3 ++- actix-files/src/path_buf.rs | 2 +- awc/src/client/connector.rs | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f0ae00b97..16c6e98da 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -24,7 +24,8 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false } actix-http = "3.0.0-beta.13" -actix-service = "2.0.0" +actix-service = "2" +actix-utils = "3" askama_escape = "0.10" bitflags = "1" diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 8c8bca6ce..0e0d4f51d 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -1,9 +1,9 @@ use std::{ - future::{ready, Ready}, path::{Path, PathBuf}, str::FromStr, }; +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, FromRequest, HttpRequest}; use crate::error::UriSegmentError; diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 54778d31e..be53437b6 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -286,12 +286,10 @@ where } #[cfg(feature = "dangerous-h2c")] { - use std::{ - future::{ready, Ready}, - io, - }; + use std::io; use actix_tls::connect::Connection; + use actix_utils::future::{ready, Ready}; impl IntoConnectionIo for TcpConnection> { fn into_connection_io(self) -> (Box, Protocol) { From e045418038a9674628d6fc5bb90d6c779826c556 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 30 Nov 2021 14:12:04 +0000 Subject: [PATCH 129/861] prepare for actix-tls rc.1 (#2474) --- CHANGES.md | 7 ++ Cargo.toml | 15 +++- README.md | 4 +- actix-files/Cargo.toml | 2 +- actix-http-test/CHANGES.md | 6 ++ actix-http-test/Cargo.toml | 6 +- actix-http-test/README.md | 4 +- actix-http/CHANGES.md | 5 ++ actix-http/Cargo.toml | 6 +- actix-http/README.md | 4 +- actix-http/src/h1/service.rs | 7 +- actix-http/src/h2/service.rs | 7 +- actix-http/src/service.rs | 7 +- actix-http/tests/test_rustls.rs | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 6 ++ awc/Cargo.toml | 10 +-- awc/README.md | 4 +- awc/src/builder.rs | 8 +- awc/src/client/connector.rs | 141 +++++++++++++++++--------------- awc/src/client/error.rs | 4 +- awc/src/client/mod.rs | 2 +- awc/src/lib.rs | 4 +- awc/tests/test_client.rs | 2 +- awc/tests/test_rustls_client.rs | 2 +- scripts/bump | 111 +++++++++++++++++++++++++ scripts/ci-test.sh | 16 ++++ src/error/response_error.rs | 2 +- src/server.rs | 10 +-- 31 files changed, 296 insertions(+), 114 deletions(-) create mode 100755 scripts/bump create mode 100755 scripts/ci-test.sh diff --git a/CHANGES.md b/CHANGES.md index 290d1dca2..00bf85f27 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.13 - 2021-11-30 +### Changed +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] + +[#2474]: https://github.com/actix/actix-web/pull/2474 + + ## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] diff --git a/Cargo.toml b/Cargo.toml index c65b0732d..425bdbbb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.12" +version = "4.0.0-beta.13" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -75,9 +75,9 @@ actix-rt = "2.3" actix-server = "2.0.0-beta.9" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-tls = { version = "3.0.0-beta.9", default-features = false, optional = true } +actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" actix-router = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.5" @@ -143,6 +143,15 @@ actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } awc = { path = "awc" } +# uncomment for quick testing against local actix-net repo +# actix-service = { path = "../actix-net/actix-service" } +# actix-macros = { path = "../actix-net/actix-macros" } +# actix-rt = { path = "../actix-net/actix-rt" } +# actix-codec = { path = "../actix-net/actix-codec" } +# actix-utils = { path = "../actix-net/actix-utils" } +# actix-tls = { path = "../actix-net/actix-tls" } +# actix-server = { path = "../actix-net/actix-server" } + [[test]] name = "test_server" required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] diff --git a/README.md b/README.md index cc68b5097..c363ece9b 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web/4.0.0-beta.12) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.13)](https://docs.rs/actix-web/4.0.0-beta.13) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.12) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.13)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 16c6e98da..6b6d6d245 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -23,7 +23,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false } -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" actix-service = "2" actix-utils = "3" diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 37de57d42..6984e5962 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.8 - 2021-11-30 +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] + +[#2474]: https://github.com/actix/actix-web/pull/2474 + + ## 3.0.0-beta.7 - 2021-11-22 * Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index cfc32b52f..8d347d4e9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.7" +version = "3.0.0-beta.8" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] @@ -31,7 +31,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.1" -actix-tls = "3.0.0-beta.9" +actix-tls = "3.0.0-rc.1" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-beta.9" @@ -52,4 +52,4 @@ tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 057cf6a13..c3e99d259 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.7)](https://docs.rs/actix-http-test/3.0.0-beta.7) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.8)](https://docs.rs/actix-http-test/3.0.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.7) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.8) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 9e004e13b..797cde99b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,18 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.14 - 2021-11-30 ### Changed * Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] * Expose `header::map` module. [#2467] * Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 +[#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.13 - 2021-11-22 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1c7cd1982..f8b15df75 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.13" +version = "3.0.0-beta.14" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] @@ -73,7 +73,7 @@ sha-1 = "0.9" smallvec = "1.6.1" # tls -actix-tls = { version = "3.0.0-beta.9", default-features = false, optional = true } +actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } # compression brotli2 = { version="0.3.2", optional = true } @@ -83,7 +83,7 @@ zstd = { version = "0.9", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.9" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.9", features = ["openssl"] } +actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" diff --git a/actix-http/README.md b/actix-http/README.md index d1f451e3e..92b86d2a3 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http/3.0.0-beta.13) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.14)](https://docs.rs/actix-http/3.0.0-beta.14) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.13) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.14) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 970c45efb..8a50417d2 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -103,7 +103,10 @@ mod openssl { use super::*; use actix_tls::accept::{ - openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + openssl::{ + reexports::{Error as SslError, SslAcceptor}, + Acceptor, TlsStream, + }, TlsError, }; @@ -164,7 +167,7 @@ mod rustls { use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - rustls::{Acceptor, ServerConfig, TlsStream}, + rustls::{reexports::ServerConfig, Acceptor, TlsStream}, TlsError, }; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 794397bfc..798740234 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -103,7 +103,10 @@ where mod openssl { use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + openssl::{ + reexports::{Error as SslError, SslAcceptor}, + Acceptor, TlsStream, + }, TlsError, }; @@ -151,7 +154,7 @@ mod rustls { use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - rustls::{Acceptor, ServerConfig, TlsStream}, + rustls::{reexports::ServerConfig, Acceptor, TlsStream}, TlsError, }; diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index aa3e54a84..a47dda738 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -197,7 +197,10 @@ where mod openssl { use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + openssl::{ + reexports::{Error as SslError, SslAcceptor}, + Acceptor, TlsStream, + }, TlsError, }; @@ -270,7 +273,7 @@ mod rustls { use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - rustls::{Acceptor, ServerConfig, TlsStream}, + rustls::{reexports::ServerConfig, Acceptor, TlsStream}, TlsError, }; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 320c9ad92..b5289bf7c 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -20,7 +20,7 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; -use actix_tls::connect::tls::rustls::webpki_roots_cert_store; +use actix_tls::connect::rustls::webpki_roots_cert_store; use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 5376ba128..7554feebf 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index afbbd0cc4..dcaa3e9a3 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" actix-http-test = "3.0.0-beta.7" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 9712e0656..e7b8cd0f0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" actix-web = { version = "4.0.0-beta.11", default-features = false } bytes = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 788dce056..ab3362b72 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.12 - 2021-11-30 +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] + +[#2474]: https://github.com/actix/actix-web/pull/2474 + + ## 3.0.0-beta.11 - 2021-11-22 * No significant changes from `3.0.0-beta.10`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index a8d0e3e33..851e5cd43 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.11" +version = "3.0.0-beta.12" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -60,9 +60,9 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.13" +actix-http = "3.0.0-beta.14" actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.0.0-beta.9", features = ["connect"] } +actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] } actix-utils = "3.0.0" ahash = "0.7" @@ -94,11 +94,11 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.13", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.14", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-beta.9" -actix-tls = { version = "3.0.0-beta.9", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } brotli2 = "0.3.2" diff --git a/awc/README.md b/awc/README.md index 7070337d0..b0faedc68 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.11)](https://docs.rs/awc/3.0.0-beta.11) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.12)](https://docs.rs/awc/3.0.0-beta.12) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.11/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.11) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.12/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.12) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/src/builder.rs b/awc/src/builder.rs index fda7d93ac..70a28c419 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -5,7 +5,7 @@ use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; use crate::{ - client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, + client::{ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection}, connect::DefaultConnector, error::SendRequestError, middleware::{NestTransform, Redirect, Transform}, @@ -33,7 +33,7 @@ impl ClientBuilder { #[allow(clippy::new_ret_no_self)] pub fn new() -> ClientBuilder< impl Service< - TcpConnect, + ConnectInfo, Response = TcpConnection, Error = TcpConnectError, > + Clone, @@ -56,7 +56,7 @@ impl ClientBuilder { impl ClientBuilder where - S: Service, Response = TcpConnection, Error = TcpConnectError> + S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, Io: ActixStream + fmt::Debug + 'static, @@ -65,7 +65,7 @@ where pub fn connector(self, connector: Connector) -> ClientBuilder where S1: Service< - TcpConnect, + ConnectInfo, Response = TcpConnection, Error = TcpConnectError, > + Clone diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index be53437b6..40b3c4d32 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -15,8 +15,8 @@ use actix_rt::{ }; use actix_service::Service; use actix_tls::connect::{ - new_connector, Connect as TcpConnect, ConnectError as TcpConnectError, - Connection as TcpConnection, Resolver, + ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection, + Connector as TcpConnector, Resolver, }; use futures_core::{future::LocalBoxFuture, ready}; use http::Uri; @@ -28,13 +28,15 @@ use super::error::ConnectError; use super::pool::ConnectionPool; use super::Connect; -enum SslConnector { - #[allow(dead_code)] +enum OurTlsConnector { + #[allow(dead_code)] // only dead when no TLS feature is enabled None, + #[cfg(feature = "openssl")] - Openssl(actix_tls::connect::ssl::openssl::SslConnector), + Openssl(actix_tls::connect::openssl::reexports::SslConnector), + #[cfg(feature = "rustls")] - Rustls(std::sync::Arc), + Rustls(std::sync::Arc), } /// Manages HTTP client network connectivity. @@ -53,21 +55,22 @@ enum SslConnector { pub struct Connector { connector: T, config: ConnectorConfig, - #[allow(dead_code)] - ssl: SslConnector, + + #[allow(dead_code)] // only dead when no TLS feature is enabled + ssl: OurTlsConnector, } impl Connector<()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< impl Service< - TcpConnect, + ConnectInfo, Response = TcpConnection, Error = actix_tls::connect::ConnectError, > + Clone, > { Connector { - connector: new_connector(resolver::resolver()), + connector: TcpConnector::new(resolver::resolver()).service(), config: ConnectorConfig::default(), ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), } @@ -75,16 +78,16 @@ impl Connector<()> { /// Provides an empty TLS connector when no TLS feature is enabled. #[cfg(not(any(feature = "openssl", feature = "rustls")))] - fn build_ssl(_: Vec>) -> SslConnector { - SslConnector::None + fn build_ssl(_: Vec>) -> OurTlsConnector { + OurTlsConnector::None } /// Build TLS connector with rustls, based on supplied ALPN protocols /// /// Note that if both `openssl` and `rustls` features are enabled, rustls will be used. #[cfg(feature = "rustls")] - fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_tls::connect::tls::rustls::{webpki_roots_cert_store, ClientConfig}; + fn build_ssl(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::rustls::{reexports::ClientConfig, webpki_roots_cert_store}; let mut config = ClientConfig::builder() .with_safe_defaults() @@ -93,13 +96,13 @@ impl Connector<()> { config.alpn_protocols = protocols; - SslConnector::Rustls(std::sync::Arc::new(config)) + OurTlsConnector::Rustls(std::sync::Arc::new(config)) } /// Build TLS connector with openssl, based on supplied ALPN protocols #[cfg(all(feature = "openssl", not(feature = "rustls")))] - fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_tls::connect::tls::openssl::{SslConnector as OpensslConnector, SslMethod}; + fn build_ssl(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod}; use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); @@ -108,12 +111,12 @@ impl Connector<()> { alpn.put(proto.as_slice()); } - let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); if let Err(err) = ssl.set_alpn_protos(&alpn) { log::error!("Can not set ALPN protocol: {:?}", err); } - SslConnector::Openssl(ssl.build()) + OurTlsConnector::Openssl(ssl.build()) } } @@ -123,7 +126,7 @@ impl Connector { where Io1: ActixStream + fmt::Debug + 'static, S1: Service< - TcpConnect, + ConnectInfo, Response = TcpConnection, Error = TcpConnectError, > + Clone, @@ -136,7 +139,7 @@ impl Connector { } } -impl Connector +impl Connector where // Note: // Input Io type is bound to ActixStream trait but internally in client module they @@ -145,8 +148,8 @@ where // // This remap is to hide ActixStream's trait methods. They are not meant to be called // from user code. - Io: ActixStream + fmt::Debug + 'static, - S: Service, Response = TcpConnection, Error = TcpConnectError> + IO: ActixStream + fmt::Debug + 'static, + S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, { @@ -166,18 +169,21 @@ where #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. - pub fn ssl(mut self, connector: actix_tls::connect::ssl::openssl::SslConnector) -> Self { - self.ssl = SslConnector::Openssl(connector); + pub fn ssl( + mut self, + connector: actix_tls::connect::openssl::reexports::SslConnector, + ) -> Self { + self.ssl = OurTlsConnector::Openssl(connector); self } #[cfg(feature = "rustls")] - /// Use custom `SslConnector` instance. + /// Use custom `ClientConfig` instance. pub fn rustls( mut self, - connector: std::sync::Arc, + connector: std::sync::Arc, ) -> Self { - self.ssl = SslConnector::Rustls(connector); + self.ssl = OurTlsConnector::Rustls(connector); self } @@ -266,7 +272,7 @@ where /// Finish configuration process and create connector service. /// The Connector builder always concludes by calling `finish()` last in /// its combinator chain. - pub fn finish(self) -> ConnectorService { + pub fn finish(self) -> ConnectorService { let local_address = self.config.local_address; let timeout = self.config.timeout; @@ -279,11 +285,12 @@ where }; let tls_service = match self.ssl { - SslConnector::None => { + OurTlsConnector::None => { #[cfg(not(feature = "dangerous-h2c"))] { None } + #[cfg(feature = "dangerous-h2c")] { use std::io; @@ -305,17 +312,17 @@ where #[derive(Clone)] struct NoOpTlsConnectorService; - impl Service> for NoOpTlsConnectorService + impl Service> for NoOpTlsConnectorService where - U: ActixStream + 'static, + IO: ActixStream + 'static, { - type Response = Connection>; + type Response = Connection>; type Error = io::Error; type Future = Ready>; actix_service::always_ready!(); - fn call(&self, connection: Connection) -> Self::Future { + fn call(&self, connection: Connection) -> Self::Future { let (io, connection) = connection.replace_io(()); let (_, connection) = connection.replace_io(Box::new(io) as _); @@ -334,13 +341,14 @@ where Some(actix_service::boxed::rc_service(tls_service)) } } + #[cfg(feature = "openssl")] - SslConnector::Openssl(tls) => { + OurTlsConnector::Openssl(tls) => { const H2: &[u8] = b"h2"; - use actix_tls::connect::ssl::openssl::{OpensslConnector, SslStream}; + use actix_tls::connect::openssl::{reexports::AsyncSslStream, TlsConnector}; - impl IntoConnectionIo for TcpConnection> { + impl IntoConnectionIo for TcpConnection> { fn into_connection_io(self) -> (Box, Protocol) { let sock = self.into_parts().0; let h2 = sock @@ -359,19 +367,20 @@ where let tls_service = TlsConnectorService { tcp_service: tcp_service_inner, - tls_service: OpensslConnector::service(tls), + tls_service: TlsConnector::service(tls), timeout: handshake_timeout, }; Some(actix_service::boxed::rc_service(tls_service)) } + #[cfg(feature = "rustls")] - SslConnector::Rustls(tls) => { + OurTlsConnector::Rustls(tls) => { const H2: &[u8] = b"h2"; - use actix_tls::connect::ssl::rustls::{RustlsConnector, TlsStream}; + use actix_tls::connect::rustls::{reexports::AsyncTlsStream, TlsConnector}; - impl IntoConnectionIo for TcpConnection> { + impl IntoConnectionIo for TcpConnection> { fn into_connection_io(self) -> (Box, Protocol) { let sock = self.into_parts().0; let h2 = sock @@ -391,7 +400,7 @@ where let tls_service = TlsConnectorService { tcp_service: tcp_service_inner, - tls_service: RustlsConnector::service(tls), + tls_service: TlsConnector::service(tls), timeout: handshake_timeout, }; @@ -460,26 +469,28 @@ where /// service for establish tcp connection and do client tls handshake. /// operation is canceled when timeout limit reached. -struct TlsConnectorService { - /// tcp connection is canceled on `TcpConnectorInnerService`'s timeout setting. - tcp_service: S, - /// tls connection is canceled on `TlsConnectorService`'s timeout setting. - tls_service: St, +struct TlsConnectorService { + /// TCP connection is canceled on `TcpConnectorInnerService`'s timeout setting. + tcp_service: Tcp, + + /// TLS connection is canceled on `TlsConnectorService`'s timeout setting. + tls_service: Tls, + timeout: Duration, } -impl Service for TlsConnectorService +impl Service for TlsConnectorService where - S: Service, Error = ConnectError> + Tcp: Service, Error = ConnectError> + Clone + 'static, - St: Service, Error = std::io::Error> + Clone + 'static, - Io: ConnectionIo, - St::Response: IntoConnectionIo, + Tls: Service, Error = std::io::Error> + Clone + 'static, + Tls::Response: IntoConnectionIo, + IO: ConnectionIo, { type Response = (Box, Protocol); type Error = ConnectError; - type Future = TlsConnectorFuture; + type Future = TlsConnectorFuture; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { ready!(self.tcp_service.poll_ready(cx))?; @@ -579,7 +590,7 @@ impl TcpConnectorInnerService { impl Service for TcpConnectorInnerService where - S: Service, Response = TcpConnection, Error = TcpConnectError> + S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, { @@ -590,7 +601,7 @@ where actix_service::forward_ready!(service); fn call(&self, req: Connect) -> Self::Future { - let mut req = TcpConnect::new(req.uri).set_addr(req.addr); + let mut req = ConnectInfo::new(req.uri).set_addr(req.addr); if let Some(local_addr) = self.local_address { req = req.set_local_addr(local_addr); @@ -629,8 +640,8 @@ where } /// Connector service for pooled Plain/Tls Tcp connections. -pub type ConnectorService = ConnectorServicePriv< - TcpConnectorService>, +pub type ConnectorService = ConnectorServicePriv< + TcpConnectorService>, Rc< dyn Service< Connect, @@ -642,7 +653,7 @@ pub type ConnectorService = ConnectorServicePriv< >, >, >, - Io, + IO, Box, >; @@ -741,7 +752,7 @@ mod resolver { use super::*; pub(super) fn resolver() -> Resolver { - Resolver::Default + Resolver::default() } } @@ -783,8 +794,7 @@ mod resolver { } } - // dns struct is cached in thread local. - // so new client constructor can reuse the existing dns resolver. + // resolver struct is cached in thread local so new clients can reuse the existing instance thread_local! { static TRUST_DNS_RESOLVER: RefCell> = RefCell::new(None); } @@ -792,8 +802,10 @@ mod resolver { // get from thread local or construct a new trust-dns resolver. TRUST_DNS_RESOLVER.with(|local| { let resolver = local.borrow().as_ref().map(Clone::clone); + match resolver { Some(resolver) => resolver, + None => { let (cfg, opts) = match read_system_conf() { Ok((cfg, opts)) => (cfg, opts), @@ -806,8 +818,9 @@ mod resolver { let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap(); // box trust dns resolver and put it in thread local. - let resolver = Resolver::new_custom(TrustDnsResolver(resolver)); + let resolver = Resolver::custom(TrustDnsResolver(resolver)); *local.borrow_mut() = Some(resolver.clone()); + resolver } } @@ -838,9 +851,9 @@ mod tests { .await; let connector = Connector { - connector: new_connector(resolver::resolver()), + connector: TcpConnector::new(resolver::resolver()).service(), config: ConnectorConfig::default(), - ssl: SslConnector::None, + ssl: OurTlsConnector::None, }; let client = Client::builder().connector(connector).finish(); diff --git a/awc/src/client/error.rs b/awc/src/client/error.rs index 0f3b1fdea..68ffb6fbf 100644 --- a/awc/src/client/error.rs +++ b/awc/src/client/error.rs @@ -7,7 +7,7 @@ use actix_http::{ http::Error as HttpError, }; #[cfg(feature = "openssl")] -use actix_tls::accept::openssl::SslError; +use actix_tls::accept::openssl::reexports::Error as OpenSslError; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] @@ -20,7 +20,7 @@ pub enum ConnectError { /// SSL error #[cfg(feature = "openssl")] #[display(fmt = "{}", _0)] - SslError(SslError), + SslError(OpenSslError), /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] diff --git a/awc/src/client/mod.rs b/awc/src/client/mod.rs index 3abbf50a5..0d5c899bc 100644 --- a/awc/src/client/mod.rs +++ b/awc/src/client/mod.rs @@ -11,7 +11,7 @@ mod h2proto; mod pool; pub use actix_tls::connect::{ - Connect as TcpConnect, ConnectError as TcpConnectError, Connection as TcpConnection, + ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection, }; pub use self::connection::{Connection, ConnectionIo}; diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 05f97aa3d..fc99419eb 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -137,7 +137,7 @@ use actix_http::{ use actix_rt::net::TcpStream; use actix_service::Service; -use self::client::{TcpConnect, TcpConnectError, TcpConnection}; +use self::client::{ConnectInfo, TcpConnectError, TcpConnection}; /// An asynchronous HTTP and WebSocket client. /// @@ -186,7 +186,7 @@ impl Client { /// This function is equivalent of `ClientBuilder::new()`. pub fn builder() -> ClientBuilder< impl Service< - TcpConnect, + ConnectInfo, Response = TcpConnection, Error = TcpConnectError, > + Clone, diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 856a4ace2..5abb63e39 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -127,7 +127,7 @@ async fn test_timeout() { }); let connector = awc::Connector::new() - .connector(actix_tls::connect::default_connector()) + .connector(actix_tls::connect::ConnectorService::default()) .timeout(Duration::from_secs(15)); let client = awc::Client::builder() diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 355fcb6fb..652997de6 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -14,7 +14,7 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt}; -use actix_tls::connect::tls::rustls::webpki_roots_cert_store; +use actix_tls::connect::rustls::webpki_roots_cert_store; use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use rustls::{ diff --git a/scripts/bump b/scripts/bump new file mode 100755 index 000000000..8b6a3c424 --- /dev/null +++ b/scripts/bump @@ -0,0 +1,111 @@ +#!/bin/sh + +# developed on macOS and probably doesn't work on Linux yet due to minor +# differences in flags on sed + +# requires github cli tool for automatic release draft creation + +set -euo pipefail + +DIR=$1 + +LINUX="" +MACOS="" + +if [ "$(uname)" = "Darwin" ]; then + MACOS="1" +fi + +CARGO_MANIFEST=$DIR/Cargo.toml +CHANGELOG_FILE=$DIR/CHANGES.md +README_FILE=$DIR/README.md + +# get current version +PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" +CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")" + +CHANGE_CHUNK_FILE="$(mktemp)" +echo saving changelog to $CHANGE_CHUNK_FILE +echo + +# get changelog chunk and save to temp file +cat "$CHANGELOG_FILE" | + # skip up to unreleased heading + sed '1,/Unreleased/ d' | + # take up to previous version heading + sed "/$CURRENT_VERSION/ q" | + # drop last line + sed '$d' \ + >"$CHANGE_CHUNK_FILE" + +# if word count of changelog chunk is 0 then insert filler changelog chunk +if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then + echo "* No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE" +fi + +if [ -n "${2-}" ]; then + NEW_VERSION="$2" +else + echo + echo "--- Changes since $CURRENT_VERSION ----" + cat "$CHANGE_CHUNK_FILE" + echo + read -p "Update version to: " NEW_VERSION +fi + +DATE="$(date -u +"%Y-%m-%d")" +echo "updating from $CURRENT_VERSION => $NEW_VERSION ($DATE)" + +# update package.version field +sed -i.bak -E "s/^version ?= ?\"[^\"]+\"$/version = \"$NEW_VERSION\"/" "$CARGO_MANIFEST" + +# update readme +[ -f "$README_FILE" ] && sed -i.bak -E "s#$CURRENT_VERSION([/)])#$NEW_VERSION\1#g" "$README_FILE" + +# update changelog file +( + sed '/Unreleased/ q' "$CHANGELOG_FILE" # up to unreleased heading + echo # blank line + echo # blank line + echo "## $NEW_VERSION - $DATE" # new version heading + cat "$CHANGE_CHUNK_FILE" # previously unreleased changes + sed "/$CURRENT_VERSION/ q" "$CHANGELOG_FILE" | tail -n 1 # the previous version heading + sed "1,/$CURRENT_VERSION/ d" "$CHANGELOG_FILE" # everything after previous version heading +) >"$CHANGELOG_FILE.bak" +mv "$CHANGELOG_FILE.bak" "$CHANGELOG_FILE" + +# done; remove backup files +rm -f $CARGO_MANIFEST.bak +rm -f $CHANGELOG_FILE.bak +rm -f $README_FILE.bak + +echo "manifest, changelog, and readme updated" +echo +echo "check other references:" +rg "$PACKAGE_NAME =" || true +rg "package = \"$PACKAGE_NAME\"" || true + +if [ $MACOS ]; then + printf "prepare $PACKAGE_NAME release $NEW_VERSION" | pbcopy +else + echo + echo "commit message:" + echo "prepare $PACKAGE_NAME release $NEW_VERSION" +fi + +SHORT_PACKAGE_NAME="$(echo $PACKAGE_NAME | sed 's/^actix-web-//' | sed 's/^actix-//')" +GIT_TAG="$(echo $SHORT_PACKAGE_NAME-v$NEW_VERSION)" +RELEASE_TITLE="$(echo $PACKAGE_NAME: v$NEW_VERSION)" + +echo +echo "GitHub release command:" +echo "gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" --prerelease" + +read -p "Submit draft GH release: (y/N) " GH_RELEASE +GH_RELEASE="${GH_RELEASE:-n}" + +if [ "$GH_RELEASE" = 'y' ] || [ "$GH_RELEASE" = 'Y' ]; then + gh release create "$GIT_TAG" --draft --title "$RELEASE_TITLE" --notes-file "$CHANGE_CHUNK_FILE" --prerelease +fi + +echo diff --git a/scripts/ci-test.sh b/scripts/ci-test.sh new file mode 100755 index 000000000..096eb7600 --- /dev/null +++ b/scripts/ci-test.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# run tests matching what CI does for non-linux feature sets + +set -x + +cargo test --lib --tests -p=actix-router --all-features +cargo test --lib --tests -p=actix-http --all-features +cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls +cargo test --lib --tests -p=actix-web-codegen --all-features +cargo test --lib --tests -p=awc --all-features +cargo test --lib --tests -p=actix-http-test --all-features +cargo test --lib --tests -p=actix-test --all-features +cargo test --lib --tests -p=actix-files +cargo test --lib --tests -p=actix-multipart --all-features +cargo test --lib --tests -p=actix-web-actors --all-features diff --git a/src/error/response_error.rs b/src/error/response_error.rs index c3c543419..2c34be3cb 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -49,7 +49,7 @@ downcast_dyn!(ResponseError); impl ResponseError for Box {} #[cfg(feature = "openssl")] -impl ResponseError for actix_tls::accept::openssl::SslError {} +impl ResponseError for actix_tls::accept::openssl::reexports::Error {} impl ResponseError for serde::de::value::Error { fn status_code(&self) -> StatusCode { diff --git a/src/server.rs b/src/server.rs index 0f3d7c59a..1bf56655b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,9 +15,9 @@ use actix_service::{ }; #[cfg(feature = "openssl")] -use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; +use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] -use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig; +use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig; use crate::{config::AppConfig, Error}; @@ -108,11 +108,11 @@ where /// [Extensions] container so that request-local data can be passed to middleware and handlers. /// /// For example: - /// - `actix_tls::openssl::SslStream` when using openssl. - /// - `actix_tls::rustls::TlsStream` when using rustls. + /// - `actix_tls::accept::openssl::TlsStream` when using openssl. + /// - `actix_tls::accept::rustls::TlsStream` when using rustls. /// - `actix_web::rt::net::TcpStream` when no encryption is used. /// - /// See `on_connect` example for additional details. + /// See the `on_connect` example for additional details. pub fn on_connect(self, f: CB) -> HttpServer where CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, From 697238fadca3b77c414eda013b1e812a932fad8c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 1 Dec 2021 00:26:07 +0000 Subject: [PATCH 130/861] prepare actix-multipart release 0.4.0-beta.9 --- actix-multipart/CHANGES.md | 8 ++++++-- actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- scripts/{ci-test.sh => ci-test} | 0 4 files changed, 9 insertions(+), 5 deletions(-) rename scripts/{ci-test.sh => ci-test} (100%) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index deb999878..d9ded57a4 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.9 - 2021-12-01 +* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] + +[#2463]: https://github.com/actix/actix-web/pull/2463 + + ## 0.4.0-beta.8 - 2021-11-22 * Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] * Added `MultipartError::NoContentDisposition` variant. [#2451] @@ -10,10 +16,8 @@ * Added `Field::name` method for getting the field name. [#2451] * `MultipartError` now marks variants with inner errors as the source. [#2451] * `MultipartError` is now marked as non-exhaustive. [#2451] -* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2451]: https://github.com/actix/actix-web/pull/2451 -[#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.7 - 2021-10-20 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 7554feebf..04a1d75ee 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.8" +version = "0.4.0-beta.9" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 75379629d..85c78c5f3 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.8)](https://docs.rs/actix-multipart/0.4.0-beta.8) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.9)](https://docs.rs/actix-multipart/0.4.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.8/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.8) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.9/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.9) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/scripts/ci-test.sh b/scripts/ci-test similarity index 100% rename from scripts/ci-test.sh rename to scripts/ci-test From 0df275c478ac2fbdce508c6d06b00bde64e217a2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 1 Dec 2021 19:42:02 +0000 Subject: [PATCH 131/861] update all IETF RFC links to new URL format --- actix-http/src/header/shared/charset.rs | 9 +- actix-http/src/header/shared/extended.rs | 16 ++-- actix-http/src/header/shared/quality_item.rs | 2 +- actix-http/src/header/utils.rs | 2 + actix-http/src/ws/proto.rs | 6 +- awc/src/ws.rs | 5 +- src/http/header/accept.rs | 8 +- src/http/header/accept_charset.rs | 7 +- src/http/header/accept_encoding.rs | 9 +- src/http/header/allow.rs | 14 +-- src/http/header/cache_control.rs | 6 +- src/http/header/content_disposition.rs | 91 +++++++++++--------- src/http/header/content_language.rs | 11 +-- src/http/header/content_range.rs | 15 ++-- src/http/header/content_type.rs | 11 +-- src/http/header/date.rs | 9 +- src/http/header/entity.rs | 10 ++- src/http/header/etag.rs | 10 +-- src/http/header/expires.rs | 8 +- src/http/header/if_match.rs | 11 +-- src/http/header/if_modified_since.rs | 9 +- src/http/header/if_none_match.rs | 11 +-- src/http/header/if_range.rs | 7 +- src/http/header/if_unmodified_since.rs | 10 +-- src/http/header/last_modified.rs | 16 ++-- src/http/header/range.rs | 33 +++---- src/service.rs | 1 - 27 files changed, 163 insertions(+), 184 deletions(-) diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index 3cc0f3e23..109c02bd1 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -1,9 +1,8 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; +use std::{fmt, str}; use self::Charset::*; -/// A Mime charset. +/// A MIME character set. /// /// The string representation is normalized to upper case. /// @@ -95,13 +94,13 @@ impl Charset { } } -impl Display for Charset { +impl fmt::Display for Charset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.label()) } } -impl FromStr for Charset { +impl str::FromStr for Charset { type Err = crate::Error; fn from_str(s: &str) -> Result { diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 9fd4cdfb0..3820b1db6 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -24,8 +24,8 @@ pub struct ExtendedValue { pub value: Vec, } -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// Parses extended header parameter values (`ext-value`), as defined +/// in [RFC 5987 §3.2](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2). /// /// Extended values are denoted by parameter names that end with `*`. /// @@ -34,7 +34,7 @@ pub struct ExtendedValue { /// ```text /// ext-value = charset "'" [ language ] "'" value-chars /// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) +/// ; (see [RFC 2231 §7]) /// /// charset = "UTF-8" / "ISO-8859-1" / mime-charset /// @@ -43,22 +43,26 @@ pub struct ExtendedValue { /// / "!" / "#" / "$" / "%" / "&" /// / "+" / "-" / "^" / "_" / "`" /// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] +/// ; as in [RFC 2978 §2.3] /// ; except that the single quote is not included /// ; SHOULD be registered in the IANA charset registry /// -/// language = +/// language = /// /// value-chars = *( pct-encoded / attr-char ) /// /// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 +/// ; see [RFC 3986 §2.1] /// /// attr-char = ALPHA / DIGIT /// / "!" / "#" / "$" / "&" / "+" / "-" / "." /// / "^" / "_" / "`" / "|" / "~" /// ; token except ( "*" / "'" / "%" ) /// ``` +/// +/// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7 +/// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3 +/// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1 pub fn parse_extended_value( val: &str, ) -> Result { diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 431e9fb3e..60a562dff 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -224,7 +224,7 @@ mod tests { } } - impl FromStr for Encoding { + impl str::FromStr for Encoding { type Err = crate::error::ParseError; fn from_str(s: &str) -> Result { use Encoding::*; diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index c40d1cc90..f4fec9335 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -13,8 +13,10 @@ where 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() { diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 8ec04a5c3..75068e239 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -3,7 +3,9 @@ use std::{ fmt, }; -/// Operation codes as part of RFC6455. +/// Operation codes defined in [RFC 6455 §11.8]. +/// +/// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8 #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum OpCode { /// Indicates a continuation frame of a fragmented message. @@ -105,7 +107,7 @@ pub enum CloseCode { Abnormal, /// Indicates that an endpoint is terminating the connection because it has received data within - /// a message that was not consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\] + /// a message that was not consistent with the type of the message (e.g., non-UTF-8 \[RFC 3629\] /// data within a text message). Invalid, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 2fe36399c..e2f1f86d0 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -312,9 +312,8 @@ impl WebsocketsRequest { ); } - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) + // Generate a random key for the `Sec-WebSocket-Key` header which is a base64-encoded + // (see RFC 4648 §4) value that, when decoded, is 16 bytes in length (RFC 6455 §1.3). let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 75366dfae..68676ba39 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -6,7 +6,8 @@ use super::{qitem, QualityItem}; use crate::http::header; crate::http::header::common_header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) + /// `Accept` header, defined + /// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify /// response media types that are acceptable. Accept header fields can @@ -15,7 +16,6 @@ crate::http::header::common_header! { /// in-line image /// /// # ABNF - /// /// ```text /// Accept = #( media-range [ accept-params ] ) /// @@ -27,7 +27,7 @@ crate::http::header::common_header! { /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] /// ``` /// - /// # Example values + /// # Example Values /// * `audio/*; q=0.2, audio/basic` /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` /// @@ -79,7 +79,7 @@ crate::http::header::common_header! { /// ``` (Accept, header::ACCEPT) => (QualityItem)+ - test_accept { + test_parse_and_format { // Tests from the RFC crate::http::header::common_header_test!( test1, diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index bb7d86516..fb21c5ac2 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -2,7 +2,7 @@ use super::{Charset, QualityItem, ACCEPT_CHARSET}; crate::http::header::common_header! { /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) + /// [RFC 7231 §5.3.3](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3) /// /// The `Accept-Charset` header field can be sent by a user agent to /// indicate what charsets are acceptable in textual response content. @@ -12,12 +12,11 @@ crate::http::header::common_header! { /// those charsets. /// /// # ABNF - /// /// ```text /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) /// ``` /// - /// # Example values + /// # Example Values /// * `iso-8859-5, unicode-1-1;q=0.8` /// /// # Examples @@ -55,7 +54,7 @@ crate::http::header::common_header! { /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - test_accept_charset { + test_parse_and_format { // Test case from RFC crate::http::header::common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index cfd29bf77..f7375a1e4 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -1,8 +1,8 @@ use header::{Encoding, QualityItem}; header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) + /// `Accept-Encoding` header, defined + /// in [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4) /// /// The `Accept-Encoding` header field can be used by user agents to /// indicate what response content-codings are @@ -11,13 +11,12 @@ header! { /// preferred. /// /// # ABNF - /// /// ```text /// Accept-Encoding = #( codings [ weight ] ) /// codings = content-coding / "identity" / "*" /// ``` /// - /// # Example values + /// # Example Values /// * `compress, gzip` /// * `` /// * `*` @@ -62,7 +61,7 @@ header! { /// ``` (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - test_accept_encoding { + test_parse_and_format { // From the RFC crate::http::header::common_header_test!(test1, vec![b"compress, gzip"]); crate::http::header::common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index 946f70e0a..2546ce3a8 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,27 +1,27 @@ -use crate::http::header; use actix_http::http::Method; +use crate::http::header; + crate::http::header::common_header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) + /// `Allow` header, defined + /// in [RFC 7231 §7.4.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is + /// supported by the target resource. The purpose of this field is /// strictly to inform the recipient of valid request methods associated /// with the resource. /// /// # ABNF - /// /// ```text /// Allow = #method /// ``` /// - /// # Example values + /// # Example Values /// * `GET, HEAD, PUT` /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` /// * `` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::{header::Allow, Method}; @@ -47,7 +47,7 @@ crate::http::header::common_header! { /// ``` (Allow, header::ALLOW) => (Method)* - test_allow { + test_parse_and_format { // From the RFC crate::http::header::common_header_test!( test1, diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 05903e3a3..c5ac9e798 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -5,7 +5,8 @@ use super::{fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, use crate::http::header; -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) +/// `Cache-Control` header, defined +/// in [RFC 7234 §5.2](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2). /// /// The `Cache-Control` header field is used to specify directives for /// caches along the request/response chain. Such cache directives are @@ -13,13 +14,12 @@ use crate::http::header; /// not imply that the same directive is to be given in the response. /// /// # ABNF -/// /// ```text /// Cache-Control = 1#cache-directive /// cache-directive = token [ "=" ( token / quoted-string ) ] /// ``` /// -/// # Example values +/// # Example Values /// /// * `no-cache` /// * `private, community="UCI"` diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 6d07a41bd..439c995ac 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -1,10 +1,14 @@ -//! # References +//! The `Content-Disposition` header and associated types. //! -//! "The Content-Disposition Header Field" -//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" -//! "Returning Values from Forms: multipart/form-data" -//! Browser conformance tests at: -//! IANA assignment: +//! # References +//! - "The Content-Disposition Header Field": +//! +//! - "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)": +//! +//! - "Returning Values from Forms: multipart/form-data": +//! +//! - Browser conformance tests at: +//! - IANA assignment: use once_cell::sync::Lazy; use regex::Regex; @@ -41,8 +45,9 @@ pub enum DispositionType { /// rather than process it normally (as per its media type). Attachment, - /// Used in *multipart/form-data* as defined in [RFC7578](https://tools.ietf.org/html/rfc7578) - /// to carry the field name and optional filename. + /// Used in *multipart/form-data* as defined in + /// [RFC 7578](https://datatracker.ietf.org/doc/html/rfc7578) to carry the field name and + /// optional filename. FormData, /// Extension type. Should be handled by recipients the same way as Attachment. @@ -82,26 +87,29 @@ pub enum DispositionParam { /// A plain file name. /// - /// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any - /// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where + /// It is [not supposed](https://datatracker.ietf.org/doc/html/rfc6266#appendix-D) to contain + /// any non-ASCII characters when used in a *Content-Disposition* HTTP response header, where /// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead /// in case there are Unicode characters in file names. Filename(String), /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). + /// [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2). FilenameExt(ExtendedValue), /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. + /// [RFC 5987 §3.2.1](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1) as + /// `reg-parameter`, in + /// [RFC 6266 §4.1](https://datatracker.ietf.org/doc/html/rfc6266#section-4.1) as + /// `token "=" value`. Recipients should ignore unrecognizable parameters. Unknown(String, String), /// An unrecognized extended parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailing asterisk is not included. Recipients should ignore unrecognizable parameters. + /// [RFC 5987 §3.2.1](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1) as + /// `ext-parameter`, in + /// [RFC 6266 §4.1](https://datatracker.ietf.org/doc/html/rfc6266#section-4.1) as + /// `ext-token "=" ext-value`. The single trailing asterisk is not included. Recipients should + /// ignore unrecognizable parameters. UnknownExt(String, ExtendedValue), } @@ -195,10 +203,10 @@ impl DispositionParam { } /// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as +/// [a response header for the main body](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#as_a_response_header_for_the_main_body) +/// as (re)defined in [RFC 6266](https://tools.ietf.org/html/rfc6266), or as /// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). +/// as (re)defined in [RFC 7587](https://tools.ietf.org/html/rfc7578). /// /// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if /// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as @@ -233,19 +241,17 @@ impl DispositionParam { /// ``` /// /// # Note +/// *filename* is [not supposed](https://datatracker.ietf.org/doc/html/rfc6266#appendix-D) to +/// contain any non-ASCII characters when used in a *Content-Disposition* HTTP response header, +/// where filename* with charset UTF-8 may be used instead in case there are Unicode characters in +/// file names. Filename is [acceptable](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2) +/// to be UTF-8 encoded directly in a *Content-Disposition* header for +/// *multipart/form-data*, though. /// -/// filename is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any -/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where -/// filename* with charset UTF-8 may be used instead in case there are Unicode characters in file -/// names. -/// filename is [acceptable](https://tools.ietf.org/html/rfc7578#section-4.2) to be UTF-8 encoded -/// directly in a *Content-Disposition* header for *multipart/form-data*, though. -/// -/// filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within +/// *filename* [must not](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2) be used within /// *multipart/form-data*. /// -/// # Example -/// +/// # Examples /// ``` /// use actix_web::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, @@ -291,10 +297,9 @@ impl DispositionParam { /// ``` /// /// # Security Note -/// /// If "filename" parameter is supplied, do not use the file name blindly, check and possibly /// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3). +/// information that may be present. See [RFC 2183](https://tools.ietf.org/html/rfc2183#section-2.3). // TODO: private fields and use smallvec #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { @@ -342,7 +347,7 @@ impl ContentDisposition { } else { // regular parameters let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 + // quoted-string: defined in RFC 6266 -> RFC 2616 Section 3.6 let mut escaping = false; let mut quoted_string = vec![]; let mut end = None; @@ -393,22 +398,22 @@ impl ContentDisposition { Ok(cd) } - /// Returns `true` if it is [`Inline`](DispositionType::Inline). + /// Returns `true` if type is [`Inline`](DispositionType::Inline). pub fn is_inline(&self) -> bool { matches!(self.disposition, DispositionType::Inline) } - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). + /// Returns `true` if type is [`Attachment`](DispositionType::Attachment). pub fn is_attachment(&self) -> bool { matches!(self.disposition, DispositionType::Attachment) } - /// Returns `true` if it is [`FormData`](DispositionType::FormData). + /// Returns `true` if type is [`FormData`](DispositionType::FormData). pub fn is_form_data(&self) -> bool { matches!(self.disposition, DispositionType::FormData) } - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. + /// Returns `true` if type is [`Ext`](DispositionType::Ext) and the `disp_type` matches. pub fn is_ext(&self, disp_type: impl AsRef) -> bool { matches!( self.disposition, @@ -487,7 +492,9 @@ impl fmt::Display for DispositionParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // All ASCII control characters (0-30, 127) including horizontal tab, double quote, and // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S3.6 + // + // Ref: RFC 6266 §4.1 -> RFC 2616 §3.6 + // // filename-parm = "filename" "=" value // value = token | quoted-string // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) @@ -501,7 +508,7 @@ impl fmt::Display for DispositionParam { // CTL = // - // Ref: RFC7578 S4.2 -> RFC2183 S2 -> RFC2045 S5.1 + // Ref: RFC 7578 S4.2 -> RFC 2183 S2 -> RFC 2045 S5.1 // parameter := attribute "=" value // attribute := token // ; Matching of attributes @@ -746,7 +753,7 @@ mod tests { #[test] fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: + /* RFC 7578 Section 4.2: Some commonly deployed systems use multipart/form-data with file names directly encoded including octets outside the US-ASCII range. The encoding used for the file names is typically UTF-8, although HTML forms will use the charset associated with the form. @@ -819,9 +826,9 @@ mod tests { #[test] fn test_from_raw_unnecessary_percent_decode() { - // In fact, RFC7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with + // In fact, RFC 7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with // non-ASCII characters MAY be percent-encoded. - // On the contrary, RFC6266 or other RFCs related to Content-Disposition response header + // On the contrary, RFC 6266 or other RFCs related to Content-Disposition response header // do not mention such percent-encoding. // So, it appears to be undecidable whether to percent-decode or not without // knowing the usage scenario (multipart/form-data v.s. HTTP response header) and diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 604ada83c..0f428ad35 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -2,8 +2,8 @@ use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; crate::http::header::common_header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) + /// `Content-Language` header, defined + /// in [RFC 7231 §3.1.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.3.2) /// /// The `Content-Language` header field describes the natural language(s) /// of the intended audience for the representation. Note that this @@ -11,18 +11,15 @@ crate::http::header::common_header! { /// representation. /// /// # ABNF - /// /// ```text /// Content-Language = 1#language-tag /// ``` /// - /// # Example values - /// + /// # Example Values /// * `da` /// * `mi, en` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; @@ -49,7 +46,7 @@ crate::http::header::common_header! { /// ``` (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - test_content_language { + test_parse_and_format { crate::http::header::common_header_test!(test1, vec![b"da"]); crate::http::header::common_header_test!(test2, vec![b"mi, en"]); } diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index 3bdead2c0..56b7f76b1 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -1,15 +1,17 @@ -use std::fmt::{self, Display, Write}; -use std::str::FromStr; +use std::{ + fmt::{self, Display, Write}, + str::FromStr, +}; use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; crate::http::header::common_header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) + /// `Content-Range` header, defined + /// in [RFC 7233 §4.2](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - test_content_range { + test_parse_and_format { crate::http::header::common_header_test!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { @@ -69,10 +71,9 @@ crate::http::header::common_header! { } } -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) +/// Content-Range, described in [RFC 7233](https://tools.ietf.org/html/rfc7233#section-4.2) /// /// # ABNF -/// /// ```text /// Content-Range = byte-content-range /// / other-content-range diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index 230460003..624a51711 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -2,8 +2,8 @@ use super::CONTENT_TYPE; use mime::Mime; crate::http::header::common_header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) + /// `Content-Type` header, defined + /// in [RFC 7231 §3.1.1.5](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5) /// /// The `Content-Type` header field indicates the media type of the /// associated representation: either the representation enclosed in the @@ -18,18 +18,15 @@ crate::http::header::common_header! { /// this is an issue, it's possible to implement `Header` on a custom struct. /// /// # ABNF - /// /// ```text /// Content-Type = media-type /// ``` /// - /// # Example values - /// + /// # Example Values /// * `text/html; charset=utf-8` /// * `application/json` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::ContentType; @@ -51,7 +48,7 @@ crate::http::header::common_header! { /// ``` (ContentType, CONTENT_TYPE) => [Mime] - test_content_type { + test_parse_and_format { crate::http::header::common_header_test!( test1, vec![b"text/html"], diff --git a/src/http/header/date.rs b/src/http/header/date.rs index 4d1717886..08c9b7ed1 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -2,19 +2,18 @@ use super::{HttpDate, DATE}; use std::time::SystemTime; crate::http::header::common_header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) + /// `Date` header, defined + /// in [RFC 7231 §7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the /// message was originated. /// /// # ABNF - /// /// ```text /// Date = HTTP-date /// ``` /// - /// # Example values - /// + /// # Example Values /// * `Tue, 15 Nov 1994 08:12:31 GMT` /// /// # Example @@ -31,7 +30,7 @@ crate::http::header::common_header! { /// ``` (Date, DATE) => [HttpDate] - test_date { + test_parse_and_format { crate::http::header::common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); } } diff --git a/src/http/header/entity.rs b/src/http/header/entity.rs index 5073ed692..ff8e17287 100644 --- a/src/http/header/entity.rs +++ b/src/http/header/entity.rs @@ -1,5 +1,7 @@ -use std::fmt::{self, Display, Write}; -use std::str::FromStr; +use std::{ + fmt::{self, Display, Write}, + str::FromStr, +}; use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; @@ -15,7 +17,8 @@ fn check_slice_validity(slice: &str) -> bool { slice.bytes().all(entity_validate_char) } -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) +/// An entity tag, defined +/// in [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3) /// /// An entity tag consists of a string enclosed by two literal double quotes. /// Preceding the first double quote is an optional weakness indicator, @@ -23,7 +26,6 @@ fn check_slice_validity(slice: &str) -> bool { /// `W/"xyzzy"`. /// /// # ABNF -/// /// ```text /// entity-tag = [ weak ] opaque-tag /// weak = %x57.2F ; "W/", case-sensitive diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index aded72665..11206407d 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -1,7 +1,8 @@ use super::{EntityTag, ETAG}; crate::http::header::common_header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) + /// `ETag` header, defined in + /// [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag /// for the selected representation, as determined at the conclusion of @@ -14,19 +15,16 @@ crate::http::header::common_header! { /// prefixed by a weakness indicator. /// /// # ABNF - /// /// ```text /// ETag = entity-tag /// ``` /// - /// # Example values - /// + /// # Example Values /// * `"xyzzy"` /// * `W/"xyzzy"` /// * `""` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::{ETag, EntityTag}; @@ -48,7 +46,7 @@ crate::http::header::common_header! { /// ``` (ETag, ETAG) => [EntityTag] - test_etag { + test_parse_and_format { // From the RFC crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""], diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index e810fe267..7ff78be85 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -1,7 +1,8 @@ use super::{HttpDate, EXPIRES}; crate::http::header::common_header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) + /// `Expires` header, defined + /// in [RFC 7234 §5.3](https://datatracker.ietf.org/doc/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the /// response is considered stale. @@ -11,12 +12,11 @@ crate::http::header::common_header! { /// time. /// /// # ABNF - /// /// ```text /// Expires = HTTP-date /// ``` /// - /// # Example values + /// # Example Values /// * `Thu, 01 Dec 1994 16:00:00 GMT` /// /// # Example @@ -34,7 +34,7 @@ crate::http::header::common_header! { /// ``` (Expires, EXPIRES) => [HttpDate] - test_expires { + test_parse_and_format { // Test case from RFC crate::http::header::common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); } diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index 87a94a809..ac06fa876 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,8 +1,8 @@ use super::{EntityTag, IF_MATCH}; crate::http::header::common_header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) + /// `If-Match` header, defined + /// in [RFC 7232 §3.1](https://datatracker.ietf.org/doc/html/rfc7232#section-3.1) /// /// The `If-Match` header field makes the request method conditional on /// the recipient origin server either having at least one current @@ -17,18 +17,15 @@ crate::http::header::common_header! { /// there have been any changes to the representation data. /// /// # ABNF - /// /// ```text /// If-Match = "*" / 1#entity-tag /// ``` /// - /// # Example values - /// + /// # Example Values /// * `"xyzzy"` /// * "xyzzy", "r2d2xxxx", "c3piozzzz" /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::IfMatch; @@ -52,7 +49,7 @@ crate::http::header::common_header! { /// ``` (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - test_if_match { + test_parse_and_format { crate::http::header::common_header_test!( test1, vec![b"\"xyzzy\""], diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index 254003523..0d23be188 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,8 +1,8 @@ use super::{HttpDate, IF_MODIFIED_SINCE}; crate::http::header::common_header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) + /// `If-Modified-Since` header, defined + /// in [RFC 7232 §3.3](https://datatracker.ietf.org/doc/html/rfc7232#section-3.3) /// /// The `If-Modified-Since` header field makes a GET or HEAD request /// method conditional on the selected representation's modification date @@ -11,12 +11,11 @@ crate::http::header::common_header! { /// data has not changed. /// /// # ABNF - /// /// ```text /// If-Unmodified-Since = HTTP-date /// ``` /// - /// # Example values + /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example @@ -34,7 +33,7 @@ crate::http::header::common_header! { /// ``` (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - test_if_modified_since { + test_parse_and_format { // Test case from RFC crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index e1422bd36..80f87ed7b 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,8 +1,8 @@ use super::{EntityTag, IF_NONE_MATCH}; crate::http::header::common_header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) + /// `If-None-Match` header, defined + /// in [RFC 7232 §3.2](https://datatracker.ietf.org/doc/html/rfc7232#section-3.2) /// /// The `If-None-Match` header field makes the request method conditional /// on a recipient cache or origin server either not having any current @@ -16,13 +16,11 @@ crate::http::header::common_header! { /// the representation data. /// /// # ABNF - /// /// ```text /// If-None-Match = "*" / 1#entity-tag /// ``` /// - /// # Example values - /// + /// # Example Values /// * `"xyzzy"` /// * `W/"xyzzy"` /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` @@ -30,7 +28,6 @@ crate::http::header::common_header! { /// * `*` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::IfNoneMatch; @@ -54,7 +51,7 @@ crate::http::header::common_header! { /// ``` (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - test_if_none_match { + test_parse_and_format { crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""]); crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""]); crate::http::header::common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index cf69e7269..9a51ab3a8 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -8,7 +8,8 @@ use crate::error::ParseError; use crate::http::header; use crate::HttpMessage; -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) +/// `If-Range` header, defined +/// in [RFC 7233 §3.2](https://datatracker.ietf.org/doc/html/rfc7233#section-3.2) /// /// If a client has a partial copy of a representation and wishes to have /// an up-to-date copy of the entire representation, it could use the @@ -24,18 +25,16 @@ use crate::HttpMessage; /// in Range; otherwise, send me the entire representation. /// /// # ABNF -/// /// ```text /// If-Range = entity-tag / HTTP-date /// ``` /// -/// # Example values +/// # Example Values /// /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// * `\"xyzzy\"` /// /// # Examples -/// /// ``` /// use actix_web::HttpResponse; /// use actix_web::http::header::{EntityTag, IfRange}; diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 1cc7b304e..d0498682b 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,8 +1,8 @@ use super::{HttpDate, IF_UNMODIFIED_SINCE}; crate::http::header::common_header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) + /// `If-Unmodified-Since` header, defined + /// in [RFC 7232 §3.4](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) /// /// The `If-Unmodified-Since` header field makes the request method /// conditional on the selected representation's last modification date @@ -11,13 +11,11 @@ crate::http::header::common_header! { /// the user agent does not have an entity-tag for the representation. /// /// # ABNF - /// /// ```text /// If-Unmodified-Since = HTTP-date /// ``` /// - /// # Example values - /// + /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example @@ -35,7 +33,7 @@ crate::http::header::common_header! { /// ``` (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - test_if_unmodified_since { + test_parse_and_format { // Test case from RFC crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index c43bf3ac9..ce5c829c2 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,8 +1,8 @@ use super::{HttpDate, LAST_MODIFIED}; crate::http::header::common_header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) + /// `Last-Modified` header, defined + /// in [RFC 7232 §2.2](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) /// /// The `Last-Modified` header field in a response provides a timestamp /// indicating the date and time at which the origin server believes the @@ -10,13 +10,11 @@ crate::http::header::common_header! { /// conclusion of handling the request. /// /// # ABNF - /// /// ```text /// Expires = HTTP-date /// ``` /// - /// # Example values - /// + /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example @@ -34,8 +32,8 @@ crate::http::header::common_header! { /// ``` (LastModified, LAST_MODIFIED) => [HttpDate] - test_last_modified { - // Test case from RFC - crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } + test_parse_and_format { + // Test case from RFC + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } } diff --git a/src/http/header/range.rs b/src/http/header/range.rs index a9b40b403..4c2ceee6f 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -4,15 +4,14 @@ use std::str::FromStr; use super::parsing::from_one_raw_str; use super::{Header, Raw}; -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) +/// `Range` header, defined +/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) /// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more sub-ranges of the -/// selected representation data, rather than the entire selected +/// The "Range" header field on a GET request modifies the method semantics to request transfer of +/// only one or more sub-ranges of the selected representation data, rather than the entire selected /// representation data. /// /// # ABNF -/// /// ```text /// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set @@ -27,8 +26,7 @@ use super::{Header, Raw}; /// last-byte-pos = 1*DIGIT /// ``` /// -/// # Example values -/// +/// # Example Values /// * `bytes=1000-` /// * `bytes=-2000` /// * `bytes=0-1,30-40` @@ -37,7 +35,6 @@ use super::{Header, Raw}; /// * `custom_unit=xxx-yyy` /// /// # Examples -/// /// ``` /// use hyper::header::{Headers, Range, ByteRangeSpec}; /// @@ -63,6 +60,7 @@ use super::{Header, Raw}; pub enum Range { /// Byte range Bytes(Vec), + /// Custom range, with unit not registered at IANA /// (`other-range-unit`: String , `other-range-set`: String) Unregistered(String, String), @@ -74,8 +72,10 @@ pub enum Range { pub enum ByteRangeSpec { /// Get all bytes between x and y ("x-y") FromTo(u64, u64), + /// Get all bytes starting from x ("x-") AllFrom(u64), + /// Get last x bytes ("-x") Last(u64), } @@ -238,9 +238,7 @@ impl FromStr for ByteRangeSpec { .or(Err(::Error::Header)) .map(ByteRangeSpec::AllFrom), (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } + (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), _ => Err(::Error::Header), }, _ => Err(::Error::Header), @@ -248,16 +246,6 @@ impl FromStr for ByteRangeSpec { } } -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - impl Header for Range { fn header_name() -> &'static str { static NAME: &'static str = "Range"; @@ -286,8 +274,7 @@ mod tests { assert_eq!(r2, r3); let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200), diff --git a/src/service.rs b/src/service.rs index 515d782d9..8ba38df43 100644 --- a/src/service.rs +++ b/src/service.rs @@ -561,7 +561,6 @@ where /// The max number of services can be grouped together is 12. /// /// # Examples -/// /// ``` /// use actix_web::{services, web, App}; /// From c4b20df56a8e769dade97bf0582a0462bd1d0b21 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Dec 2021 03:45:04 +0000 Subject: [PATCH 132/861] convert all remaining IETF RFC links to new format --- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/encoder.rs | 7 ++++--- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/header/mod.rs | 2 +- actix-http/src/header/shared/extended.rs | 6 +++--- actix-http/src/header/shared/quality_item.rs | 8 ++++---- actix-http/src/header/utils.rs | 2 +- actix-http/src/ws/proto.rs | 3 ++- actix-multipart/src/error.rs | 2 +- actix-multipart/src/server.rs | 4 ++-- awc/src/client/h1proto.rs | 3 +-- awc/src/client/h2proto.rs | 2 +- src/http/header/accept.rs | 4 ++-- src/http/header/accept_language.rs | 4 ++-- src/http/header/content_disposition.rs | 9 +++++---- src/http/header/content_range.rs | 3 ++- src/http/header/range.rs | 4 ++-- 17 files changed, 35 insertions(+), 32 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 91a3af44f..f25c35a76 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -174,7 +174,7 @@ pub(crate) trait MessageType: Sized { self.set_expect() } - // https://tools.ietf.org/html/rfc7230#section-3.3.3 + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 if chunked { // Chunked encoding Ok(PayloadLength::Payload(PayloadType::Payload( diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index cc26a200f..60880cd7d 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -71,15 +71,16 @@ pub(crate) trait MessageType: Sized { | StatusCode::PROCESSING | StatusCode::NO_CONTENT => { // skip content-length and transfer-encoding headers - // see https://tools.ietf.org/html/rfc7230#section-3.3.1 - // and https://tools.ietf.org/html/rfc7230#section-3.3.2 + // see https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 + // and https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 skip_len = true; length = BodySize::None } StatusCode::NOT_MODIFIED => { // 304 responses should never have a body but should retain a manually set - // content-length header see https://tools.ietf.org/html/rfc7232#section-4.1 + // content-length header + // see https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 skip_len = false; length = BodySize::None; } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8b922b2cd..8efd3e831 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -304,7 +304,7 @@ fn prepare_response( for (key, value) in head.headers.iter() { match *key { // TODO: consider skipping other headers according to: - // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 + // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 // omit HTTP/1.x only headers CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 125f7ef16..721f4ea79 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -66,7 +66,7 @@ impl From for HeaderMap { } /// This encode set is used for HTTP header values and is defined at -/// . +/// . pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 3820b1db6..5ff5c8b34 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -1,17 +1,17 @@ +// Originally from hyper v0.11.27 src/header/parsing.rs + 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). +/// It is defined in [RFC 5987 §3.2](https://datatracker.ietf.org/doc/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. diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 60a562dff..ad1f6877d 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -26,8 +26,8 @@ const MAX_FLOAT_QUALITY: f32 = 1.0; /// a value between 0 and 1000 e.g. `Quality(532)` matches the quality /// `q=0.532`. /// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. +/// [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more +/// information on quality values in HTTP header fields. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); @@ -79,8 +79,8 @@ impl TryFrom for Quality { } } -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). +/// Represents an item with a quality value as defined +/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). #[derive(Clone, PartialEq, Debug)] pub struct QualityItem { /// The actual contents of the field. diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index f4fec9335..94c5b6dcb 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -59,7 +59,7 @@ where } /// Percent encode a sequence of bytes with a character set defined in -/// +/// #[inline] pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 75068e239..4227f221d 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,7 +222,8 @@ impl> From<(CloseCode, T)> for CloseReason { } } -/// The WebSocket GUID as stated in the spec. See . +/// The WebSocket GUID as stated in the spec. +/// See . static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; /// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec. diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index de4594b8f..7d0da35e0 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -10,7 +10,7 @@ use derive_more::{Display, Error, From}; pub enum MultipartError { /// Content-Disposition header is not found or is not equal to "form-data". /// - /// According to [RFC 7578](https://tools.ietf.org/html/rfc7578#section-4.2) a + /// According to [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2) a /// Content-Disposition header must always be present and equal to "form-data". #[display(fmt = "No Content-Disposition `form-data` header")] NoContentDisposition, diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 319e79863..23397b7ee 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -337,8 +337,8 @@ impl InnerMultipart { return Poll::Pending; }; - // According to [RFC 7578](https://tools.ietf.org/html/rfc7578#section-4.2) a - // Content-Disposition header must always be present and set to "form-data". + // According to RFC 7578 §4.2, a Content-Disposition header must always be present and + // set to "form-data". let content_disposition = headers .get(&header::CONTENT_DISPOSITION) diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 7f3ba1b6e..c442cd4cf 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -66,8 +66,7 @@ where let mut framed = Framed::new(io, h1::ClientCodec::default()); // Check EXPECT header and enable expect handle flag accordingly. - // - // RFC: https://tools.ietf.org/html/rfc7231#section-5.1.1 + // See https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1 let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Sized(0) => { diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs index 2618e1908..66fb790a3 100644 --- a/awc/src/client/h2proto.rs +++ b/awc/src/client/h2proto.rs @@ -90,7 +90,7 @@ where for (key, value) in headers { match *key { // TODO: consider skipping other headers according to: - // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 + // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 // omit HTTP/1.x only headers CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 68676ba39..3c4d5f599 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -153,7 +153,7 @@ impl Accept { /// Returns a sorted list of mime types from highest to lowest preference, accounting for /// [q-factor weighting] and specificity. /// - /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn mime_precedence(&self) -> Vec { let mut types = self.0.clone(); @@ -203,7 +203,7 @@ impl Accept { /// /// Returns `None` if contained list is empty. /// - /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn mime_preference(&self) -> Option { let types = self.mime_precedence(); types.first().cloned() diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 1552f6578..a7b60e366 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -3,8 +3,8 @@ use language_tags::LanguageTag; use super::{QualityItem, ACCEPT_LANGUAGE}; crate::http::header::common_header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) + /// `Accept-Language` header, defined + /// in [RFC 7231 §5.3.5](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5) /// /// The `Accept-Language` header field can be used by user agents to /// indicate the set of natural languages that are preferred in the diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 439c995ac..79fdb7658 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -204,9 +204,9 @@ impl DispositionParam { /// A *Content-Disposition* header. It is compatible to be used either as /// [a response header for the main body](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#as_a_response_header_for_the_main_body) -/// as (re)defined in [RFC 6266](https://tools.ietf.org/html/rfc6266), or as +/// as (re)defined in [RFC 6266](https://datatracker.ietf.org/doc/html/rfc6266), or as /// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC 7587](https://tools.ietf.org/html/rfc7578). +/// as (re)defined in [RFC 7587](https://datatracker.ietf.org/doc/html/rfc7578). /// /// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if /// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as @@ -299,8 +299,9 @@ impl DispositionParam { /// # Security Note /// If "filename" parameter is supplied, do not use the file name blindly, check and possibly /// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC 2183](https://tools.ietf.org/html/rfc2183#section-2.3). -// TODO: private fields and use smallvec +/// information that may be present. +/// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). +// TODO: think about using private fields and smallvec #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { /// The disposition type diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index 56b7f76b1..9966a2582 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -71,7 +71,8 @@ crate::http::header::common_header! { } } -/// Content-Range, described in [RFC 7233](https://tools.ietf.org/html/rfc7233#section-4.2) +/// Content-Range header, defined +/// in [RFC 7233 §4.2](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2) /// /// # ABNF /// ```text diff --git a/src/http/header/range.rs b/src/http/header/range.rs index 4c2ceee6f..f57bac912 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -93,7 +93,7 @@ impl ByteRangeSpec { /// simply ignore the range header and serve the full entity using a `200 /// OK` status code. /// - /// This function closely follows [RFC 7233][1] section 2.1. + /// This function closely follows [RFC 7233 §2.1]. /// As such, it considers ranges to be satisfiable if they meet the /// following conditions: /// @@ -112,7 +112,7 @@ impl ByteRangeSpec { /// value of last-byte-pos with a value that is one less than the current /// length of the selected representation). /// - /// [1]: https://tools.ietf.org/html/rfc7233 + /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233 pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { // If the full length is zero, there is no satisfiable end-inclusive range. if full_length == 0 { From 075d871e6392d5970908d95e1e043cf16aac1e3b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Dec 2021 13:59:25 +0000 Subject: [PATCH 133/861] wrap `LanguageTags` type in new `AnyOrSome` type to support wildcards (#2480) --- CHANGES.md | 11 + actix-http/src/header/mod.rs | 2 +- actix-http/src/header/shared/quality_item.rs | 15 +- actix-http/src/header/utils.rs | 31 ++- src/http/header/accept.rs | 51 +++-- src/http/header/accept_language.rs | 210 ++++++++++++++++--- src/http/header/macros.rs | 138 +++++++----- src/http/header/mod.rs | 77 ++++--- src/http/header/preference.rs | 70 +++++++ 9 files changed, 469 insertions(+), 136 deletions(-) create mode 100644 src/http/header/preference.rs diff --git a/CHANGES.md b/CHANGES.md index 00bf85f27..36a56b828 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] + +### Changed +* Rename `Accept::{mime_precedence => ranked}`. [#2480] +* Rename `Accept::{mime_preference => preference}`. [#2480] + +### Fixed +* Accept wildcard `*` items in `AcceptLanguage`. [#2480] + +[#2480]: https://github.com/actix/actix-web/pull/2480 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 721f4ea79..308cb0123 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -55,7 +55,7 @@ pub trait Header: IntoHeaderValue { fn name() -> HeaderName; /// Parse a header - fn parse(msg: &T) -> Result; + fn parse(msg: &M) -> Result; } /// Convert `http::HeaderMap` to our `HeaderMap`. diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index ad1f6877d..b9cca9112 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,8 +1,7 @@ use std::{ cmp, convert::{TryFrom, TryInto}, - fmt, - str::{self, FromStr}, + fmt, str, }; use derive_more::{Display, Error}; @@ -83,16 +82,17 @@ impl TryFrom for Quality { /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). #[derive(Clone, PartialEq, Debug)] pub struct QualityItem { - /// The actual contents of the field. + /// The wrapped contents of the field. pub item: T, + /// The quality (client or server preference) for the value. pub quality: Quality, } impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. + /// Constructs a new `QualityItem` from an item and a quality value. + /// + /// The item can be of any type. The quality should be a value in the range [0, 1]. pub fn new(item: T, quality: Quality) -> QualityItem { QualityItem { item, quality } } @@ -116,7 +116,7 @@ impl fmt::Display for QualityItem { } } -impl FromStr for QualityItem { +impl str::FromStr for QualityItem { type Err = ParseError; fn from_str(qitem_str: &str) -> Result { @@ -128,6 +128,7 @@ impl FromStr for QualityItem { let mut raw_item = qitem_str; let mut quality = 1f32; + // TODO: MSRV(1.52): use rsplit_once let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); if parts.len() == 2 { diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index 94c5b6dcb..2168202b9 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -12,7 +12,8 @@ where I: Iterator + 'a, T: FromStr, { - let mut result = Vec::new(); + let size_guess = all.size_hint().1.unwrap_or(2); + let mut result = Vec::with_capacity(size_guess); for h in all { let s = h.to_str().map_err(|_| ParseError::Header)?; @@ -26,6 +27,7 @@ where .filter_map(|x| x.trim().parse().ok()), ) } + Ok(result) } @@ -34,10 +36,12 @@ where 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) } @@ -48,13 +52,16 @@ 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(()) } @@ -65,3 +72,25 @@ pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Res let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); fmt::Display::fmt(&encoded, f) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn comma_delimited_parsing() { + let headers = vec![]; + let res: Vec = from_comma_delimited(headers.iter()).unwrap(); + assert_eq!(res, vec![0; 0]); + + let headers = vec![ + HeaderValue::from_static(""), + HeaderValue::from_static(","), + HeaderValue::from_static(" "), + HeaderValue::from_static("1 ,"), + HeaderValue::from_static(""), + ]; + let res: Vec = from_comma_delimited(headers.iter()).unwrap(); + assert_eq!(res, vec![1]); + } +} diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 3c4d5f599..bc794c02c 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -77,7 +77,7 @@ crate::http::header::common_header! { /// ]) /// ); /// ``` - (Accept, header::ACCEPT) => (QualityItem)+ + (Accept, header::ACCEPT) => (QualityItem)* test_parse_and_format { // Tests from the RFC @@ -88,6 +88,7 @@ crate::http::header::common_header! { QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); + crate::http::header::common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], @@ -99,6 +100,7 @@ crate::http::header::common_header! { q(800)), qitem("text/x-c".parse().unwrap()), ]))); + // Custom tests crate::http::header::common_header_test!( test3, @@ -154,7 +156,11 @@ impl Accept { /// [q-factor weighting] and specificity. /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 - pub fn mime_precedence(&self) -> Vec { + pub fn ranked(&self) -> Vec { + if self.is_empty() { + return vec![]; + } + let mut types = self.0.clone(); // use stable sort so items with equal q-factor and specificity retain listed order @@ -201,12 +207,29 @@ impl Accept { /// If no q-factors are provided, the first mime type is chosen. Note that items without /// q-factors are given the maximum preference value. /// - /// Returns `None` if contained list is empty. + /// As per the spec, will return [`Mime::STAR_STAR`] (indicating no preference) if the contained + /// list is empty. /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 - pub fn mime_preference(&self) -> Option { - let types = self.mime_precedence(); - types.first().cloned() + pub fn preference(&self) -> Mime { + use actix_http::header::q; + + let mut max_item = None; + let mut max_pref = q(0); + + // uses manual max lookup loop since we want the first occurrence in the case of same + // preference but `Iterator::max_by_key` would give us the last occurrence + + for pref in &self.0 { + // only change if strictly greater + // equal items, even while unsorted, still have higher preference if they appear first + if pref.quality > max_pref { + max_pref = pref.quality; + max_item = Some(pref.item.clone()); + } + } + + max_item.unwrap_or(mime::STAR_STAR) } } @@ -216,12 +239,12 @@ mod tests { use crate::http::header::q; #[test] - fn test_mime_precedence() { + fn ranking_precedence() { let test = Accept(vec![]); - assert!(test.mime_precedence().is_empty()); + assert!(test.ranked().is_empty()); let test = Accept(vec![qitem(mime::APPLICATION_JSON)]); - assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON)); + assert_eq!(test.ranked(), vec!(mime::APPLICATION_JSON)); let test = Accept(vec![ qitem(mime::TEXT_HTML), @@ -230,7 +253,7 @@ mod tests { QualityItem::new(mime::STAR_STAR, q(0.8)), ]); assert_eq!( - test.mime_precedence(), + test.ranked(), vec![ mime::TEXT_HTML, "application/xhtml+xml".parse().unwrap(), @@ -245,20 +268,20 @@ mod tests { qitem(mime::IMAGE_PNG), ]); assert_eq!( - test.mime_precedence(), + test.ranked(), vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR] ); } #[test] - fn test_mime_preference() { + fn preference_selection() { let test = Accept(vec![ qitem(mime::TEXT_HTML), "application/xhtml+xml".parse().unwrap(), QualityItem::new("application/xml".parse().unwrap(), q(0.9)), QualityItem::new(mime::STAR_STAR, q(0.8)), ]); - assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML)); + assert_eq!(test.preference(), mime::TEXT_HTML); let test = Accept(vec![ QualityItem::new("video/*".parse().unwrap(), q(0.8)), @@ -267,6 +290,6 @@ mod tests { qitem(mime::IMAGE_SVG), QualityItem::new(mime::IMAGE_STAR, q(0.8)), ]); - assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG)); + assert_eq!(test.preference(), mime::IMAGE_PNG); } } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index a7b60e366..e96d1d13e 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,66 +1,224 @@ use language_tags::LanguageTag; -use super::{QualityItem, ACCEPT_LANGUAGE}; +use super::{common_header, Preference, QualityItem}; +use crate::http::header; -crate::http::header::common_header! { +common_header! { /// `Accept-Language` header, defined /// in [RFC 7231 §5.3.5](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5) /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. + /// The `Accept-Language` header field can be used by user agents to indicate the set of natural + /// languages that are preferred in the response. + /// + /// The `Accept-Language` header is defined in + /// [RFC 7231 §5.3.5](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5) using language + /// ranges defined in [RFC 4647 §2.1](https://datatracker.ietf.org/doc/html/rfc4647#section-2.1). /// /// # ABNF - /// /// ```text /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = + /// language-range = (1*8ALPHA *("-" 1*8alphanum)) / "*" + /// alphanum = ALPHA / DIGIT + /// weight = OWS ";" OWS "q=" qvalue + /// qvalue = ( "0" [ "." 0*3DIGIT ] ) + /// / ( "1" [ "." 0*3("0") ] ) /// ``` /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` + /// # Example Values + /// - `da, en-gb;q=0.8, en;q=0.7` + /// - `en-us;q=1.0, en;q=0.5, fr` + /// - `fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5` /// /// # Examples - /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_web::http::header::{AcceptLanguage, qitem}; /// /// let mut builder = HttpResponse::Ok(); - /// let langtag = LanguageTag::parse("en-US").unwrap(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem(langtag), + /// qitem("en-US".parse().unwrap()) /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, LanguageTag, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem(LanguageTag::parse("da").unwrap()), - /// QualityItem::new(LanguageTag::parse("en-GB").unwrap(), q(800)), - /// QualityItem::new(LanguageTag::parse("en").unwrap(), q(700)), + /// qitem("da".parse().unwrap()), + /// QualityItem::new("en-GB".parse().unwrap(), q(800)), + /// QualityItem::new("en".parse().unwrap(), q(700)), /// ]) /// ); /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ + (AcceptLanguage, header::ACCEPT_LANGUAGE) => (QualityItem>)* - test_accept_language { - // From the RFC - crate::http::header::common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - crate::http::header::common_header_test!( - test2, vec![b"en-US, en; q=0.5, fr"], + parse_and_fmt_tests { + common_header_test!(no_headers, vec![b""; 0], Some(AcceptLanguage(vec![]))); + + common_header_test!(empty_header, vec![b""; 1], Some(AcceptLanguage(vec![]))); + + common_header_test!( + example_from_rfc, + vec![b"da, en-gb;q=0.8, en;q=0.7"] + ); + + common_header_test!( + not_ordered_by_weight, + vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ qitem("en-US".parse().unwrap()), QualityItem::new("en".parse().unwrap(), q(500)), qitem("fr".parse().unwrap()), - ]))); + ])) + ); + + common_header_test!( + has_wildcard, + vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"], + Some(AcceptLanguage(vec![ + qitem("fr-CH".parse().unwrap()), + QualityItem::new("fr".parse().unwrap(), q(900)), + QualityItem::new("en".parse().unwrap(), q(800)), + QualityItem::new("de".parse().unwrap(), q(700)), + QualityItem::new("*".parse().unwrap(), q(500)), + ])) + ); + } +} + +impl AcceptLanguage { + /// Returns a sorted list of languages from highest to lowest precedence, accounting + /// for [q-factor weighting]. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn ranked(&self) -> Vec> { + if self.0.is_empty() { + return vec![]; + } + + let mut types = self.0.clone(); + + // use stable sort so items with equal q-factor retain listed order + types.sort_by(|a, b| { + // sort by q-factor descending + b.quality.cmp(&a.quality) + }); + + types.into_iter().map(|qitem| qitem.item).collect() + } + + /// Extracts the most preferable language, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first language is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// As per the spec, returns [`Preference::Any`] if contained list is empty. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn preference(&self) -> Preference { + use actix_http::header::q; + + let mut max_item = None; + let mut max_pref = q(0); + + // uses manual max lookup loop since we want the first occurrence in the case of same + // preference but `Iterator::max_by_key` would give us the last occurrence + + for pref in &self.0 { + // only change if strictly greater + // equal items, even while unsorted, still have higher preference if they appear first + if pref.quality > max_pref { + max_pref = pref.quality; + max_item = Some(pref.item.clone()); + } + } + + max_item.unwrap_or(Preference::Any) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header::*; + + #[test] + fn ranking_precedence() { + let test = AcceptLanguage(vec![]); + assert!(test.ranked().is_empty()); + + let test = AcceptLanguage(vec![qitem("fr-CH".parse().unwrap())]); + assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap())); + + let test = AcceptLanguage(vec![ + QualityItem::new("fr".parse().unwrap(), q(900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1000)), + QualityItem::new("en".parse().unwrap(), q(800)), + QualityItem::new("*".parse().unwrap(), q(500)), + QualityItem::new("de".parse().unwrap(), q(700)), + ]); + assert_eq!( + test.ranked(), + vec![ + "fr-CH".parse().unwrap(), + "fr".parse().unwrap(), + "en".parse().unwrap(), + "de".parse().unwrap(), + "*".parse().unwrap(), + ] + ); + + let test = AcceptLanguage(vec![ + qitem("fr".parse().unwrap()), + qitem("fr-CH".parse().unwrap()), + qitem("en".parse().unwrap()), + qitem("*".parse().unwrap()), + qitem("de".parse().unwrap()), + ]); + assert_eq!( + test.ranked(), + vec![ + "fr".parse().unwrap(), + "fr-CH".parse().unwrap(), + "en".parse().unwrap(), + "*".parse().unwrap(), + "de".parse().unwrap(), + ] + ); + } + + #[test] + fn preference_selection() { + let test = AcceptLanguage(vec![ + QualityItem::new("fr".parse().unwrap(), q(900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1000)), + QualityItem::new("en".parse().unwrap(), q(800)), + QualityItem::new("*".parse().unwrap(), q(500)), + QualityItem::new("de".parse().unwrap(), q(700)), + ]); + assert_eq!( + test.preference(), + Preference::Specific("fr-CH".parse().unwrap()) + ); + + let test = AcceptLanguage(vec![ + qitem("fr".parse().unwrap()), + qitem("fr-CH".parse().unwrap()), + qitem("en".parse().unwrap()), + qitem("*".parse().unwrap()), + qitem("de".parse().unwrap()), + ]); + assert_eq!( + test.preference(), + Preference::Specific("fr".parse().unwrap()) + ); + + let test = AcceptLanguage(vec![]); + assert_eq!(test.preference(), Preference::Any); } } diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 419d4fb6e..7fed7f286 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -1,33 +1,36 @@ +// TODO: replace with derive_more impl macro_rules! common_header_deref { ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { + impl ::core::ops::Deref for $from { type Target = $to; #[inline] - fn deref(&self) -> &$to { + fn deref(&self) -> &Self::Target { &self.0 } } - impl ::std::ops::DerefMut for $from { + impl ::core::ops::DerefMut for $from { #[inline] - fn deref_mut(&mut self) -> &mut $to { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } }; } +/// Sets up a test module with some useful imports for use with [`common_header_test!`]. macro_rules! common_header_test_module { ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] #[cfg(test)] mod $tm { + #![allow(unused_imports)] + use std::str; use actix_http::http::Method; use mime::*; use $crate::http::header::*; - use super::$id as HeaderField; + use super::{$id as HeaderField, *}; $($tf)* } } @@ -42,14 +45,19 @@ macro_rules! common_header_test { let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { req = req.insert_header((HeaderField::name(), item)).take(); } + let req = req.finish(); let value = HeaderField::parse(&req); + let result = format!("{}", value.unwrap()); let expected = String::from_utf8(raw[0].to_vec()).unwrap(); + let result_cmp: Vec = result .to_ascii_lowercase() .split(' ') @@ -60,10 +68,12 @@ macro_rules! common_header_test { .split(' ') .map(|x| x.to_owned()) .collect(); + assert_eq!(result_cmp.concat(), expected_cmp.concat()); } }; - ($id:ident, $raw:expr, $typed:expr) => { + + ($id:ident, $raw:expr, $exp:expr) => { #[test] fn $id() { use actix_http::test; @@ -75,26 +85,35 @@ macro_rules! common_header_test { } let req = req.finish(); let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { + let exp: Option = $exp; + + println!("req: {:?}", &req); + println!("val: {:?}", &val); + println!("exp: {:?}", &exp); + + // test parsing + assert_eq!(val.ok(), exp); + + // test formatting + if let Some(exp) = exp { let raw = &($raw)[..]; let mut iter = raw.iter().map(|b| str::from_utf8(&b[..]).unwrap()); let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); + if let Some(s) = iter.next() { joined.push_str(s); + for s in iter { + joined.push_str(", "); + joined.push_str(s); + } } - assert_eq!(format!("{}", typed.unwrap()), joined); + assert_eq!(format!("{}", exp), joined); } } }; } macro_rules! common_header { + // TODO: these docs are wrong, there's no $n or $nn // $a:meta: Attributes associated with the header item (usually docs) // $id:ident: Identifier of the header // $n:expr: Lowercase name of the header @@ -111,92 +130,100 @@ macro_rules! common_header { fn name() -> $crate::http::header::HeaderName { $name } + #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) + fn parse(msg: &M) -> Result { + let headers = msg.headers().get_all(Self::name()); + $crate::http::header::from_comma_delimited(headers).map($id) } } - impl std::fmt::Display for $id { + + impl ::core::fmt::Display for $id { #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { $crate::http::header::fmt_comma_delimited(f, &self.0[..]) } } + impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; + use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) } } }; + // List header, one or more items ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub Vec<$item>); + crate::http::header::common_header_deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { $name } #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { + fn parse(msg: &M) -> Result { $crate::http::header::from_comma_delimited( msg.headers().get_all(Self::name())).map($id) } } - impl std::fmt::Display for $id { + + impl ::core::fmt::Display for $id { #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { $crate::http::header::fmt_comma_delimited(f, &self.0[..]) } } + impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; + use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) } } }; + // Single value header ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub $value); + crate::http::header::common_header_deref!($id => $value); + impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { $name } + #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) + fn parse(msg: &M) -> Result { + let header = msg.headers().get(Self::name()); + $crate::http::header::from_one_raw_str(header).map($id) } } - impl std::fmt::Display for $id { + + impl ::core::fmt::Display for $id { #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Display::fmt(&self.0, f) } } + impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; @@ -205,6 +232,7 @@ macro_rules! common_header { } } }; + // List header, one or more items with "*" option ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { $(#[$a])* @@ -215,42 +243,46 @@ macro_rules! common_header { /// Only the listed items are a match Items(Vec<$item>), } + impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { $name } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - if let Some(true) = any { + #[inline] + fn parse(msg: &M) -> Result { + let is_any = msg + .headers() + .get(Self::name()) + .and_then(|hdr| hdr.to_str().ok()) + .map(|hdr| hdr.trim() == "*"); + + if let Some(true) = is_any { Ok($id::Any) } else { - Ok($id::Items( - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) + let headers = msg.headers().get_all(Self::name()); + Ok($id::Items($crate::http::header::from_comma_delimited(headers)?)) } } } - impl std::fmt::Display for $id { + + impl ::core::fmt::Display for $id { #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { match *self { $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( - f, &fields[..]) + $id::Items(ref fields) => + $crate::http::header::fmt_comma_delimited(f, &fields[..]) } } } + impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; + use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 79ba5772b..45d5b8d1a 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -1,15 +1,52 @@ //! A Collection of Header implementations for common HTTP Headers. //! -//! ## Mime -//! +//! ## Mime Types //! Several header fields use MIME values for their contents. Keeping with the strongly-typed theme, //! the [mime] crate is used in such headers as [`ContentType`] and [`Accept`]. -use bytes::{Bytes, BytesMut}; use std::fmt; -pub use self::accept_charset::AcceptCharset; +use bytes::{Bytes, BytesMut}; + +// re-export from actix-http +// - header name / value types +// - relevant traits for converting to header name / value +// - all const header names +// - header map +// - the few typed headers from actix-http +// - header parsing utils pub use actix_http::http::header::*; + +mod accept_charset; +// mod accept_encoding; +mod accept; +mod accept_language; +mod allow; +mod cache_control; +mod content_disposition; +mod content_language; +mod content_range; +mod content_type; +mod date; +mod encoding; +mod entity; +mod etag; +mod expires; +mod if_match; +mod if_modified_since; +mod if_none_match; +mod if_range; +mod if_unmodified_since; +mod last_modified; +mod macros; +mod preference; +// mod range; + +#[cfg(test)] +pub(crate) use macros::common_header_test; +pub(crate) use macros::{common_header, common_header_deref, common_header_test_module}; + +pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; pub use self::accept_language::AcceptLanguage; @@ -30,11 +67,10 @@ pub use self::if_none_match::IfNoneMatch; pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; +pub use self::preference::Preference; //pub use self::range::{Range, ByteRangeSpec}; -pub(crate) use actix_http::http::header::{ - fmt_comma_delimited, from_comma_delimited, from_one_raw_str, -}; +/// Format writer ([`fmt::Write`]) for a [`BytesMut`]. #[derive(Debug, Default)] struct Writer { buf: BytesMut, @@ -62,30 +98,3 @@ impl fmt::Write for Writer { fmt::write(self, args) } } - -mod accept_charset; -// mod accept_encoding; -mod accept; -mod accept_language; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod encoding; -mod entity; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; - -mod macros; -#[cfg(test)] -pub(crate) use macros::common_header_test; -pub(crate) use macros::{common_header, common_header_deref, common_header_test_module}; diff --git a/src/http/header/preference.rs b/src/http/header/preference.rs new file mode 100644 index 000000000..979fc7720 --- /dev/null +++ b/src/http/header/preference.rs @@ -0,0 +1,70 @@ +use std::{ + fmt::{self, Write as _}, + str, +}; + +/// A wrapper for types used in header values where wildcard (`*`) items are allowed but the +/// underlying type does not support them. +/// +/// For example, we use the `language-tags` crate for the [`AcceptLanguage`](super::AcceptLanguage) +/// typed header but it does not parse `*` successfully. On the other hand, the `mime` crate, used +/// for [`Accept`](super::Accept), has first-party support for wildcard items so this wrapper is not +/// used in those header types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub enum Preference { + /// A wildcard value. + Any, + + /// A valid `T`. + Specific(T), +} + +impl Preference { + /// Returns true if preference is the any/wildcard (`*`) value. + pub fn is_any(&self) -> bool { + matches!(self, Self::Any) + } + + /// Returns true if preference is the specific item (`T`) variant. + pub fn is_specific(&self) -> bool { + matches!(self, Self::Specific(_)) + } + + /// Returns reference to value in `Specific` variant, if it is set. + pub fn item(&self) -> Option<&T> { + match self { + Preference::Specific(ref item) => Some(item), + Preference::Any => None, + } + } + + /// Consumes the container, returning the value in the `Specific` variant, if it is set. + pub fn into_item(self) -> Option { + match self { + Preference::Specific(item) => Some(item), + Preference::Any => None, + } + } +} + +impl fmt::Display for Preference { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Preference::Any => f.write_char('*'), + Preference::Specific(item) => fmt::Display::fmt(item, f), + } + } +} + +impl str::FromStr for Preference { + type Err = T::Err; + + #[inline] + fn from_str(s: &str) -> Result { + match s.trim() { + "*" => Ok(Self::Any), + other => other.parse().map(Preference::Specific), + } + } +} From 2a72bdae0991c49203b9359eade740e4de0881ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Dec 2021 15:25:39 +0000 Subject: [PATCH 134/861] improve typed header macro (#2481) --- actix-http/src/header/shared/charset.rs | 2 +- actix-http/src/header/shared/extended.rs | 2 +- actix-http/src/header/shared/http_date.rs | 2 +- actix-http/src/header/shared/quality_item.rs | 4 +- actix-web-codegen/src/lib.rs | 4 +- src/http/header/accept.rs | 2 +- src/http/header/accept_charset.rs | 2 +- src/http/header/accept_encoding.rs | 8 ++- src/http/header/accept_language.rs | 4 +- src/http/header/allow.rs | 2 +- src/http/header/any_or_some.rs | 70 ++++++++++++++++++ src/http/header/cache_control.rs | 10 +-- src/http/header/content_disposition.rs | 2 +- src/http/header/content_language.rs | 7 +- src/http/header/content_range.rs | 4 +- src/http/header/content_type.rs | 4 +- src/http/header/date.rs | 2 +- src/http/header/entity.rs | 2 +- src/http/header/etag.rs | 2 +- src/http/header/expires.rs | 2 +- src/http/header/if_match.rs | 6 +- src/http/header/if_modified_since.rs | 2 +- src/http/header/if_none_match.rs | 2 +- src/http/header/if_range.rs | 7 +- src/http/header/if_unmodified_since.rs | 2 +- src/http/header/last_modified.rs | 2 +- src/http/header/macros.rs | 74 ++++++-------------- src/http/header/mod.rs | 2 +- src/http/header/range.rs | 13 ++-- 29 files changed, 147 insertions(+), 100 deletions(-) create mode 100644 src/http/header/any_or_some.rs diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index 109c02bd1..1e77e1be8 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -7,7 +7,7 @@ use self::Charset::*; /// The string representation is normalized to upper case. /// /// See . -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(non_camel_case_types)] pub enum Charset { /// US ASCII diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 5ff5c8b34..b2cf1d754 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -31,7 +31,7 @@ pub struct ExtendedValue { /// /// ## ABNF /// -/// ```text +/// ```plain /// ext-value = charset "'" [ language ] "'" value-chars /// ; like RFC 2231's /// ; (see [RFC 2231 §7]) diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 3441f90af..8dbdf4a62 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -8,7 +8,7 @@ use crate::{ helpers::MutWriter, }; -/// A timestamp with HTTP formatting and parsing. +/// A timestamp with HTTP-style formatting and parsing. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HttpDate(SystemTime); diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index b9cca9112..9b170f01a 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -27,7 +27,7 @@ const MAX_FLOAT_QUALITY: f32 = 1.0; /// /// [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more /// information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); impl Quality { @@ -80,7 +80,7 @@ impl TryFrom for Quality { /// Represents an item with a quality value as defined /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct QualityItem { /// The wrapped contents of the field. pub item: T, diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 85faf6bca..cebf9e5fb 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -66,7 +66,7 @@ mod route; /// Creates resource handler, allowing multiple HTTP method guards. /// /// # Syntax -/// ```text +/// ```plain /// #[route("path", method="HTTP_METHOD"[, attributes])] /// ``` /// @@ -112,7 +112,7 @@ concat!(" Creates route handler with `actix_web::guard::", stringify!($variant), "`. # Syntax -```text +```plain #[", stringify!($method), r#"("path"[, attributes])] ``` diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index bc794c02c..a0c98547d 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -16,7 +16,7 @@ crate::http::header::common_header! { /// in-line image /// /// # ABNF - /// ```text + /// ```plain /// Accept = #( media-range [ accept-params ] ) /// /// media-range = ( "*/*" diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index fb21c5ac2..5577ab604 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -12,7 +12,7 @@ crate::http::header::common_header! { /// those charsets. /// /// # ABNF - /// ```text + /// ```plain /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) /// ``` /// diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index f7375a1e4..0440153ae 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -1,3 +1,5 @@ +// TODO: reinstate module + use header::{Encoding, QualityItem}; header! { @@ -11,7 +13,7 @@ header! { /// preferred. /// /// # ABNF - /// ```text + /// ```plain /// Accept-Encoding = #( codings [ weight ] ) /// codings = content-coding / "identity" / "*" /// ``` @@ -59,15 +61,17 @@ header! { /// ]) /// ); /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* + (AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem)* test_parse_and_format { // From the RFC crate::http::header::common_header_test!(test1, vec![b"compress, gzip"]); crate::http::header::common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); crate::http::header::common_header_test!(test3, vec![b"*"]); + // Note: Removed quality 1 from gzip crate::http::header::common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); + // Note: Removed quality 1 from gzip crate::http::header::common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index e96d1d13e..fb1637eb1 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -15,7 +15,7 @@ common_header! { /// ranges defined in [RFC 4647 §2.1](https://datatracker.ietf.org/doc/html/rfc4647#section-2.1). /// /// # ABNF - /// ```text + /// ```plain /// Accept-Language = 1#( language-range [ weight ] ) /// language-range = (1*8ALPHA *("-" 1*8alphanum)) / "*" /// alphanum = ALPHA / DIGIT @@ -57,7 +57,7 @@ common_header! { /// ``` (AcceptLanguage, header::ACCEPT_LANGUAGE) => (QualityItem>)* - parse_and_fmt_tests { + test_parse_and_format { common_header_test!(no_headers, vec![b""; 0], Some(AcceptLanguage(vec![]))); common_header_test!(empty_header, vec![b""; 1], Some(AcceptLanguage(vec![]))); diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index 2546ce3a8..c8cc153e8 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -12,7 +12,7 @@ crate::http::header::common_header! { /// with the resource. /// /// # ABNF - /// ```text + /// ```plain /// Allow = #method /// ``` /// diff --git a/src/http/header/any_or_some.rs b/src/http/header/any_or_some.rs new file mode 100644 index 000000000..e5a37e495 --- /dev/null +++ b/src/http/header/any_or_some.rs @@ -0,0 +1,70 @@ +use std::{ + fmt::{self, Write as _}, + str, +}; + +/// A wrapper for types used in header values where wildcard (`*`) items are allowed but the +/// underlying type does not support them. +/// +/// For example, we use the `language-tags` crate for the [`AcceptLanguage`](super::AcceptLanguage) +/// typed header but it does parse `*` successfully. On the other hand, the `mime` crate, used for +/// [`Accept`](super::Accept), has first-party support for wildcard items so this wrapper is not +/// used in those header types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub enum AnyOrSome { + /// A wildcard value. + Any, + + /// A valid `T`. + Item(T), +} + +impl AnyOrSome { + /// Returns true if item is wildcard (`*`) variant. + pub fn is_any(&self) -> bool { + matches!(self, Self::Any) + } + + /// Returns true if item is a valid item (`T`) variant. + pub fn is_item(&self) -> bool { + matches!(self, Self::Item(_)) + } + + /// Returns reference to value in `Item` variant, if it is set. + pub fn item(&self) -> Option<&T> { + match self { + AnyOrSome::Item(ref item) => Some(item), + AnyOrSome::Any => None, + } + } + + /// Consumes the container, returning the value in the `Item` variant, if it is set. + pub fn into_item(self) -> Option { + match self { + AnyOrSome::Item(item) => Some(item), + AnyOrSome::Any => None, + } + } +} + +impl fmt::Display for AnyOrSome { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AnyOrSome::Any => f.write_char('*'), + AnyOrSome::Item(item) => fmt::Display::fmt(item, f), + } + } +} + +impl str::FromStr for AnyOrSome { + type Err = T::Err; + + #[inline] + fn from_str(s: &str) -> Result { + match s.trim() { + "*" => Ok(Self::Any), + other => other.parse().map(AnyOrSome::Item), + } + } +} diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index c5ac9e798..27cf30ce4 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,6 +1,8 @@ use std::fmt::{self, Write}; use std::str::FromStr; +use derive_more::{Deref, DerefMut}; + use super::{fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer}; use crate::http::header; @@ -14,7 +16,7 @@ use crate::http::header; /// not imply that the same directive is to be given in the response. /// /// # ABNF -/// ```text +/// ```plain /// Cache-Control = 1#cache-directive /// cache-directive = token [ "=" ( token / quoted-string ) ] /// ``` @@ -46,11 +48,9 @@ use crate::http::header; /// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), /// ])); /// ``` -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)] pub struct CacheControl(pub Vec); -crate::http::header::common_header_deref!(CacheControl => Vec); - // TODO: this could just be the crate::http::header::common_header! macro impl Header for CacheControl { fn name() -> header::HeaderName { @@ -88,7 +88,7 @@ impl IntoHeaderValue for CacheControl { } /// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum CacheDirective { /// "no-cache" NoCache, diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 79fdb7658..945a58f7f 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -220,7 +220,7 @@ impl DispositionParam { /// itself, *Content-Disposition* has no effect. /// /// # ABNF -/// ```text +/// ```plain /// content-disposition = "Content-Disposition" ":" /// disposition-type *( ";" disposition-parm ) /// diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 0f428ad35..39ca8da56 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,8 @@ -use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -crate::http::header::common_header! { +use super::{common_header, QualityItem, CONTENT_LANGUAGE}; + +common_header! { /// `Content-Language` header, defined /// in [RFC 7231 §3.1.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.3.2) /// @@ -11,7 +12,7 @@ crate::http::header::common_header! { /// representation. /// /// # ABNF - /// ```text + /// ```plain /// Content-Language = 1#language-tag /// ``` /// diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index 9966a2582..90b3f7fe2 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -75,7 +75,7 @@ crate::http::header::common_header! { /// in [RFC 7233 §4.2](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2) /// /// # ABNF -/// ```text +/// ```plain /// Content-Range = byte-content-range /// / other-content-range /// @@ -91,7 +91,7 @@ crate::http::header::common_header! { /// other-content-range = other-range-unit SP other-range-resp /// other-range-resp = *CHAR /// ``` -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ContentRangeSpec { /// Byte range Bytes { diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index 624a51711..1fc75d0e2 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -18,7 +18,7 @@ crate::http::header::common_header! { /// this is an issue, it's possible to implement `Header` on a custom struct. /// /// # ABNF - /// ```text + /// ```plain /// Content-Type = media-type /// ``` /// @@ -110,5 +110,3 @@ impl ContentType { ContentType(mime::APPLICATION_OCTET_STREAM) } } - -impl Eq for ContentType {} diff --git a/src/http/header/date.rs b/src/http/header/date.rs index 08c9b7ed1..4063deab1 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -9,7 +9,7 @@ crate::http::header::common_header! { /// message was originated. /// /// # ABNF - /// ```text + /// ```plain /// Date = HTTP-date /// ``` /// diff --git a/src/http/header/entity.rs b/src/http/header/entity.rs index ff8e17287..50b40b7b2 100644 --- a/src/http/header/entity.rs +++ b/src/http/header/entity.rs @@ -26,7 +26,7 @@ fn check_slice_validity(slice: &str) -> bool { /// `W/"xyzzy"`. /// /// # ABNF -/// ```text +/// ```plain /// entity-tag = [ weak ] opaque-tag /// weak = %x57.2F ; "W/", case-sensitive /// opaque-tag = DQUOTE *etagc DQUOTE diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index 11206407d..4724c917e 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -15,7 +15,7 @@ crate::http::header::common_header! { /// prefixed by a weakness indicator. /// /// # ABNF - /// ```text + /// ```plain /// ETag = entity-tag /// ``` /// diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 7ff78be85..5b6c65c53 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -12,7 +12,7 @@ crate::http::header::common_header! { /// time. /// /// # ABNF - /// ```text + /// ```plain /// Expires = HTTP-date /// ``` /// diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index ac06fa876..a565b9125 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ -use super::{EntityTag, IF_MATCH}; +use super::{common_header, EntityTag, IF_MATCH}; -crate::http::header::common_header! { +common_header! { /// `If-Match` header, defined /// in [RFC 7232 §3.1](https://datatracker.ietf.org/doc/html/rfc7232#section-3.1) /// @@ -17,7 +17,7 @@ crate::http::header::common_header! { /// there have been any changes to the representation data. /// /// # ABNF - /// ```text + /// ```plain /// If-Match = "*" / 1#entity-tag /// ``` /// diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index 0d23be188..14d6c3553 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -11,7 +11,7 @@ crate::http::header::common_header! { /// data has not changed. /// /// # ABNF - /// ```text + /// ```plain /// If-Unmodified-Since = HTTP-date /// ``` /// diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index 80f87ed7b..fb1895fc8 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -16,7 +16,7 @@ crate::http::header::common_header! { /// the representation data. /// /// # ABNF - /// ```text + /// ```plain /// If-None-Match = "*" / 1#entity-tag /// ``` /// diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 9a51ab3a8..5af9255f6 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -25,7 +25,7 @@ use crate::HttpMessage; /// in Range; otherwise, send me the entire representation. /// /// # ABNF -/// ```text +/// ```plain /// If-Range = entity-tag / HTTP-date /// ``` /// @@ -107,10 +107,11 @@ impl IntoHeaderValue for IfRange { } #[cfg(test)] -mod test_if_range { +mod test_parse_and_format { + use std::str; + use super::IfRange as HeaderField; use crate::http::header::*; - use std::str; crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); crate::http::header::common_header_test!(test2, vec![b"\"abc\""]); diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index d0498682b..0df6d7ba0 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -11,7 +11,7 @@ crate::http::header::common_header! { /// the user agent does not have an entity-tag for the representation. /// /// # ABNF - /// ```text + /// ```plain /// If-Unmodified-Since = HTTP-date /// ``` /// diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index ce5c829c2..e15443ed1 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -10,7 +10,7 @@ crate::http::header::common_header! { /// conclusion of handling the request. /// /// # ABNF - /// ```text + /// ```plain /// Expires = HTTP-date /// ``` /// diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 7fed7f286..d91d1d282 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -1,25 +1,3 @@ -// TODO: replace with derive_more impl -macro_rules! common_header_deref { - ($from:ty => $to:ty) => { - impl ::core::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl ::core::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - }; -} - -/// Sets up a test module with some useful imports for use with [`common_header_test!`]. macro_rules! common_header_test_module { ($id:ident, $tm:ident{$($tf:item)*}) => { #[cfg(test)] @@ -87,10 +65,6 @@ macro_rules! common_header_test { let val = HeaderField::parse(&req); let exp: Option = $exp; - println!("req: {:?}", &req); - println!("val: {:?}", &val); - println!("exp: {:?}", &exp); - // test parsing assert_eq!(val.ok(), exp); @@ -114,17 +88,17 @@ macro_rules! common_header_test { macro_rules! common_header { // TODO: these docs are wrong, there's no $n or $nn - // $a:meta: Attributes associated with the header item (usually docs) + // $attrs:meta: Attributes associated with the header item (usually docs) // $id:ident: Identifier of the header // $n:expr: Lowercase name of the header // $nn:expr: Nice name of the header // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] + ($(#[$attrs:meta])*($id:ident, $name:expr) => ($item:ty)*) => { + $(#[$attrs])* + #[derive(Debug, Clone, PartialEq, Eq, ::derive_more::Deref, ::derive_more::DerefMut)] pub struct $id(pub Vec<$item>); - crate::http::header::common_header_deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -158,13 +132,11 @@ macro_rules! common_header { }; // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] + ($(#[$attrs:meta])*($id:ident, $name:expr) => ($item:ty)+) => { + $(#[$attrs])* + #[derive(Debug, Clone, PartialEq, Eq, ::derive_more::Deref, ::derive_more::DerefMut)] pub struct $id(pub Vec<$item>); - crate::http::header::common_header_deref!($id => Vec<$item>); - impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -197,13 +169,11 @@ macro_rules! common_header { }; // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] + ($(#[$attrs:meta])*($id:ident, $name:expr) => [$value:ty]) => { + $(#[$attrs])* + #[derive(Debug, Clone, PartialEq, Eq, ::derive_more::Deref, ::derive_more::DerefMut)] pub struct $id(pub $value); - crate::http::header::common_header_deref!($id => $value); - impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -234,8 +204,8 @@ macro_rules! common_header { }; // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* + ($(#[$attrs:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { + $(#[$attrs])* #[derive(Clone, Debug, PartialEq)] pub enum $id { /// Any value is a match @@ -291,32 +261,32 @@ macro_rules! common_header { }; // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { + ($(#[$attrs:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { crate::http::header::common_header! { - $(#[$a])* + $(#[$attrs])* ($id, $name) => ($item)* } crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { + ($(#[$attrs:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { crate::http::header::common_header! { - $(#[$a])* + $(#[$attrs])* ($id, $n) => ($item)+ } crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { + ($(#[$attrs:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { crate::http::header::common_header! { - $(#[$a])* ($id, $name) => [$item] + $(#[$attrs])* ($id, $name) => [$item] } crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { + ($(#[$attrs:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { crate::http::header::common_header! { - $(#[$a])* + $(#[$attrs])* ($id, $name) => {Any / ($item)+} } @@ -324,7 +294,7 @@ macro_rules! common_header { }; } -pub(crate) use {common_header, common_header_deref, common_header_test_module}; +pub(crate) use {common_header, common_header_test_module}; #[cfg(test)] pub(crate) use common_header_test; diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 45d5b8d1a..750f0e5b9 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -44,7 +44,7 @@ mod preference; #[cfg(test)] pub(crate) use macros::common_header_test; -pub(crate) use macros::{common_header, common_header_deref, common_header_test_module}; +pub(crate) use macros::{common_header, common_header_test_module}; pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; diff --git a/src/http/header/range.rs b/src/http/header/range.rs index f57bac912..11006ffff 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -1,8 +1,11 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; +// TODO: reinstate module -use super::parsing::from_one_raw_str; -use super::{Header, Raw}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +use super::{parsing::from_one_raw_str, Header, Raw}; /// `Range` header, defined /// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) @@ -12,7 +15,7 @@ use super::{Header, Raw}; /// representation data. /// /// # ABNF -/// ```text +/// ```plain /// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set /// other-range-set = 1*VCHAR From deece8d519512a83a98c7b8b1cdcd664fc0ee03a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Dec 2021 17:04:40 +0000 Subject: [PATCH 135/861] re-instate accept-encoding typed header (#2482) --- CHANGES.md | 3 + actix-http/src/header/shared/quality_item.rs | 2 +- actix-http/src/header/utils.rs | 7 + scripts/ci-test | 2 + src/http/header/accept.rs | 5 +- src/http/header/accept_encoding.rs | 23 +- src/http/header/accept_language.rs | 1 + src/http/header/cache_control.rs | 290 +++++++------------ src/http/header/encoding.rs | 13 +- src/http/header/macros.rs | 51 +++- src/http/header/mod.rs | 8 +- 11 files changed, 190 insertions(+), 215 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 36a56b828..c754d4dd6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] +* `AcceptEncoding` typed header. [#2482] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -10,8 +11,10 @@ ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] +* Typed headers containing lists that require one or more items now enforce this minimum. [#2482] [#2480]: https://github.com/actix/actix-web/pull/2480 +[#2482]: https://github.com/actix/actix-web/pull/2482 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 9b170f01a..a109b44ea 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -27,7 +27,7 @@ const MAX_FLOAT_QUALITY: f32 = 1.0; /// /// [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more /// information on quality values in HTTP header fields. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); impl Quality { diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index 2168202b9..a23f5b751 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -83,6 +83,13 @@ mod tests { let res: Vec = from_comma_delimited(headers.iter()).unwrap(); assert_eq!(res, vec![0; 0]); + let headers = vec![ + HeaderValue::from_static("1, 2"), + HeaderValue::from_static("3,4"), + ]; + let res: Vec = from_comma_delimited(headers.iter()).unwrap(); + assert_eq!(res, vec![1, 2, 3, 4]); + let headers = vec![ HeaderValue::from_static(""), HeaderValue::from_static(","), diff --git a/scripts/ci-test b/scripts/ci-test index 096eb7600..98e13927d 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -14,3 +14,5 @@ cargo test --lib --tests -p=actix-test --all-features cargo test --lib --tests -p=actix-files cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features + +cargo test --workspace --doc diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index a0c98547d..fe291c011 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -118,8 +118,9 @@ crate::http::header::common_header! { #[test] fn test_fuzzing1() { - use actix_http::test::TestRequest; - let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish(); + let req = test::TestRequest::default() + .insert_header((header::ACCEPT, "chunk#;e")) + .finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 0440153ae..85cd0a4f7 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -1,8 +1,9 @@ -// TODO: reinstate module +use actix_http::header::QualityItem; -use header::{Encoding, QualityItem}; +use super::{common_header, Encoding}; +use crate::http::header; -header! { +common_header! { /// `Accept-Encoding` header, defined /// in [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4) /// @@ -30,7 +31,7 @@ header! { /// use actix_web::HttpResponse; /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut builder = HttpResponse::new(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) /// ); @@ -39,7 +40,7 @@ header! { /// use actix_web::HttpResponse; /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; /// - /// let mut builder = HttpResponse::new(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), @@ -52,7 +53,7 @@ header! { /// use actix_web::HttpResponse; /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem}; /// - /// let mut builder = HttpResponse::new(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), @@ -65,14 +66,14 @@ header! { test_parse_and_format { // From the RFC - crate::http::header::common_header_test!(test1, vec![b"compress, gzip"]); - crate::http::header::common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - crate::http::header::common_header_test!(test3, vec![b"*"]); + common_header_test!(test1, vec![b"compress, gzip"]); + common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + common_header_test!(test3, vec![b"*"]); // Note: Removed quality 1 from gzip - crate::http::header::common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); + common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip - crate::http::header::common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index fb1637eb1..229f95ef1 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -67,6 +67,7 @@ common_header! { vec![b"da, en-gb;q=0.8, en;q=0.7"] ); + common_header_test!( not_ordered_by_weight, vec![b"en-US, en; q=0.5, fr"], diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 27cf30ce4..490d36558 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,92 +1,97 @@ -use std::fmt::{self, Write}; -use std::str::FromStr; - -use derive_more::{Deref, DerefMut}; - -use super::{fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer}; +use std::{fmt, str}; +use super::common_header; use crate::http::header; -/// `Cache-Control` header, defined -/// in [RFC 7234 §5.2](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2). -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// ```plain -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example Values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ``` -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ``` -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.insert_header(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)] -pub struct CacheControl(pub Vec); +common_header! { + /// `Cache-Control` header, defined + /// in [RFC 7234 §5.2](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2). + /// + /// The `Cache-Control` header field is used to specify directives for + /// caches along the request/response chain. Such cache directives are + /// unidirectional in that the presence of a directive in a request does + /// not imply that the same directive is to be given in the response. + /// + /// # ABNF + /// ```text + /// Cache-Control = 1#cache-directive + /// cache-directive = token [ "=" ( token / quoted-string ) ] + /// ``` + /// + /// # Example Values + /// * `no-cache` + /// * `private, community="UCI"` + /// * `max-age=30` + /// + /// # Examples + /// ``` + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{CacheControl, CacheDirective}; + /// + /// let mut builder = HttpResponse::Ok(); + /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); + /// ``` + /// + /// ``` + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{CacheControl, CacheDirective}; + /// + /// let mut builder = HttpResponse::Ok(); + /// builder.insert_header(CacheControl(vec![ + /// CacheDirective::NoCache, + /// CacheDirective::Private, + /// CacheDirective::MaxAge(360u32), + /// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), + /// ])); + /// ``` + (CacheControl, header::CACHE_CONTROL) => (CacheDirective)+ -// TODO: this could just be the crate::http::header::common_header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } + test_parse_and_format { + common_header_test!(no_headers, vec![b""; 0], None); + common_header_test!(empty_header, vec![b""; 1], None); + common_header_test!(bad_syntax, vec![b"foo="], None); - #[inline] - fn parse(msg: &T) -> Result - where - T: crate::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(crate::error::ParseError::Header) + common_header_test!( + multiple_headers, + vec![&b"no-cache"[..], &b"private"[..]], + Some(CacheControl(vec![ + CacheDirective::NoCache, + CacheDirective::Private, + ])) + ); + + common_header_test!( + argument, + vec![b"max-age=100, private"], + Some(CacheControl(vec![ + CacheDirective::MaxAge(100), + CacheDirective::Private, + ])) + ); + + common_header_test!( + extension, + vec![b"foo, bar=baz"], + Some(CacheControl(vec![ + CacheDirective::Extension("foo".to_owned(), None), + CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), + ])) + ); + + #[test] + fn parse_quote_form() { + let req = test::TestRequest::default() + .insert_header((header::CACHE_CONTROL, "max-age=\"200\"")) + .finish(); + + assert_eq!( + Header::parse(&req).ok(), + Some(CacheControl(vec![CacheDirective::MaxAge(200)])) + ) } } } -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_comma_delimited(f, &self.0[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValue; - - fn try_into_value(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_maybe_shared(writer.take()) - } -} - /// `CacheControl` contains a list of these directives. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CacheDirective { @@ -126,38 +131,40 @@ pub enum CacheDirective { impl fmt::Display for CacheDirective { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), + let dir_str = match self { + NoCache => "no-cache", + NoStore => "no-store", + NoTransform => "no-transform", + OnlyIfCached => "only-if-cached", - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + MaxAge(secs) => return write!(f, "max-age={}", secs), + MaxStale(secs) => return write!(f, "max-stale={}", secs), + MinFresh(secs) => return write!(f, "min-fresh={}", secs), - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg); - } - }, - f, - ) + MustRevalidate => "must-revalidate", + Public => "public", + Private => "private", + ProxyRevalidate => "proxy-revalidate", + SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + + Extension(name, None) => name.as_str(), + Extension(name, Some(arg)) => return write!(f, "{}={}", name, arg), + }; + + f.write_str(dir_str) } } -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { +impl str::FromStr for CacheDirective { + type Err = Option<::Err>; + + fn from_str(s: &str) -> Result { use self::CacheDirective::*; + match s { + "" => Err(None), + "no-cache" => Ok(NoCache), "no-store" => Ok(NoStore), "no-transform" => Ok(NoTransform), @@ -166,7 +173,7 @@ impl FromStr for CacheDirective { "public" => Ok(Public), "private" => Ok(Private), "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), + _ => match s.find('=') { Some(idx) if idx + 1 < s.len() => { match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { @@ -183,76 +190,3 @@ impl FromStr for CacheDirective { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::http::header::Header; - use actix_http::test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::default() - .insert_header((header::CACHE_CONTROL, "no-cache, private")) - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = TestRequest::default() - .insert_header((header::CACHE_CONTROL, "max-age=100, private")) - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = TestRequest::default() - .insert_header((header::CACHE_CONTROL, "max-age=\"200\"")) - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = TestRequest::default() - .insert_header((header::CACHE_CONTROL, "foo, bar=baz")) - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - 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/src/http/header/encoding.rs b/src/http/header/encoding.rs index ce31c100f..a61edda67 100644 --- a/src/http/header/encoding.rs +++ b/src/http/header/encoding.rs @@ -4,26 +4,33 @@ pub use self::Encoding::{ Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, Zstd, }; -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] +/// A value to represent an encoding used in `Transfer-Encoding` or `Accept-Encoding` header. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Encoding { /// The `chunked` encoding. Chunked, + /// The `br` encoding. Brotli, + /// The `gzip` encoding. Gzip, + /// The `deflate` encoding. Deflate, + /// The `compress` encoding. Compress, + /// The `identity` encoding. Identity, + /// The `trailers` encoding. Trailers, + /// The `zstd` encoding. Zstd, + /// Some other encoding that is less common, can be any String. EncodingExt(String), } diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index d91d1d282..3f530658c 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -4,11 +4,14 @@ macro_rules! common_header_test_module { mod $tm { #![allow(unused_imports)] - use std::str; - use actix_http::http::Method; - use mime::*; - use $crate::http::header::*; + use ::core::str; + + use ::actix_http::{http::Method, test}; + use ::mime::*; + + use $crate::http::header::{self, *}; use super::{$id as HeaderField, *}; + $($tf)* } } @@ -19,22 +22,22 @@ macro_rules! common_header_test { ($id:ident, $raw:expr) => { #[test] fn $id() { - use actix_http::test; + use ::actix_http::test; let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); + let headers = raw.iter().map(|x| x.to_vec()).collect::>(); let mut req = test::TestRequest::default(); - for item in a { - req = req.insert_header((HeaderField::name(), item)).take(); + for item in headers { + req = req.append_header((HeaderField::name(), item)).take(); } let req = req.finish(); let value = HeaderField::parse(&req); let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); + let expected = ::std::string::String::from_utf8(raw[0].to_vec()).unwrap(); let result_cmp: Vec = result .to_ascii_lowercase() @@ -56,14 +59,17 @@ macro_rules! common_header_test { fn $id() { use actix_http::test; - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); + let headers = $raw.iter().map(|x| x.to_vec()).collect::>(); let mut req = test::TestRequest::default(); - for item in a { - req.insert_header((HeaderField::name(), item)); + + for item in headers { + req.append_header((HeaderField::name(), item)); } + let req = req.finish(); let val = HeaderField::parse(&req); - let exp: Option = $exp; + + let exp: ::core::option::Option = $exp; // test parsing assert_eq!(val.ok(), exp); @@ -122,6 +128,7 @@ macro_rules! common_header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; + #[inline] fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); @@ -142,10 +149,19 @@ macro_rules! common_header { fn name() -> $crate::http::header::HeaderName { $name } + #[inline] - fn parse(msg: &M) -> Result { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) + fn parse(msg: &M) -> Result{ + let headers = msg.headers().get_all(Self::name()); + + $crate::http::header::from_comma_delimited(headers) + .and_then(|items| { + if items.is_empty() { + Err($crate::error::ParseError::Header) + } else { + Ok($id(items)) + } + }) } } @@ -159,6 +175,7 @@ macro_rules! common_header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; + #[inline] fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); @@ -197,6 +214,7 @@ macro_rules! common_header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; + #[inline] fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { self.0.try_into_value() } @@ -251,6 +269,7 @@ macro_rules! common_header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; + #[inline] fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use ::core::fmt::Write; let mut writer = $crate::http::header::Writer::new(); diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 750f0e5b9..98548dadd 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -17,9 +17,9 @@ use bytes::{Bytes, BytesMut}; // - header parsing utils pub use actix_http::http::header::*; -mod accept_charset; -// mod accept_encoding; mod accept; +mod accept_charset; +mod accept_encoding; mod accept_language; mod allow; mod cache_control; @@ -46,9 +46,9 @@ mod preference; pub(crate) use macros::common_header_test; pub(crate) use macros::{common_header, common_header_test_module}; -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; +pub use self::accept_charset::AcceptCharset; +pub use self::accept_encoding::AcceptEncoding; pub use self::accept_language::AcceptLanguage; pub use self::allow::Allow; pub use self::cache_control::{CacheControl, CacheDirective}; From a2d5c5a0580d3e0d9e1cbb59396ec24159573384 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 3 Dec 2021 02:16:34 +0800 Subject: [PATCH 136/861] Use cilent time out for h2 handshake timeout. (#2483) --- actix-http/CHANGES.md | 4 + actix-http/src/h2/dispatcher.rs | 13 ++- actix-http/src/h2/mod.rs | 55 ++++++++- actix-http/src/h2/service.rs | 13 +-- actix-http/src/service.rs | 11 +- actix-http/tests/test_h2_ping_pong.rs | 77 ------------- actix-http/tests/test_h2_timer.rs | 153 ++++++++++++++++++++++++++ 7 files changed, 231 insertions(+), 95 deletions(-) delete mode 100644 actix-http/tests/test_h2_ping_pong.rs create mode 100644 actix-http/tests/test_h2_timer.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 797cde99b..1eaccfb2e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,7 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +[#2483]: https://github.com/actix/actix-web/pull/2483 ## 3.0.0-beta.14 - 2021-11-30 ### Changed diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8efd3e831..607997eb7 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -10,7 +10,7 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::Sleep; +use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; @@ -55,9 +55,16 @@ where on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, + timer: Option>>, ) -> Self { - let ping_pong = config.keep_alive_timer().map(|timer| H2PingPong { - timer: Box::pin(timer), + let ping_pong = config.keep_alive().map(|dur| H2PingPong { + timer: timer + .map(|mut timer| { + // reset timer if it's received from new function. + timer.as_mut().reset(config.now() + dur); + timer + }) + .unwrap_or_else(|| Box::pin(sleep(dur))), on_flight: false, ping_pong: connection.ping_pong().unwrap(), }); diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index 7eff44ac1..25d53403e 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,20 +1,30 @@ //! HTTP/2 protocol. use std::{ + future::Future, pin::Pin, task::{Context, Poll}, }; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::time::Sleep; use bytes::Bytes; use futures_core::{ready, Stream}; -use h2::RecvStream; +use h2::{ + server::{handshake, Connection, Handshake}, + RecvStream, +}; mod dispatcher; mod service; pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; -use crate::error::PayloadError; + +use crate::{ + config::ServiceConfig, + error::{DispatchError, PayloadError}, +}; /// HTTP/2 peer stream. pub struct Payload { @@ -50,3 +60,44 @@ impl Stream for Payload { } } } + +pub(crate) fn handshake_with_timeout( + io: T, + config: &ServiceConfig, +) -> HandshakeWithTimeout +where + T: AsyncRead + AsyncWrite + Unpin, +{ + HandshakeWithTimeout { + handshake: handshake(io), + timer: config.client_timer().map(Box::pin), + } +} + +pub(crate) struct HandshakeWithTimeout { + handshake: Handshake, + timer: Option>>, +} + +impl Future for HandshakeWithTimeout +where + T: AsyncRead + AsyncWrite + Unpin, +{ + type Output = Result<(Connection, Option>>), DispatchError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + match Pin::new(&mut this.handshake).poll(cx)? { + // return the timer on success handshake. It can be re-used for h2 ping-pong. + Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))), + Poll::Pending => match this.timer.as_mut() { + Some(timer) => { + ready!(timer.as_mut().poll(cx)); + Poll::Ready(Err(DispatchError::SlowRequestTimeout)) + } + None => Poll::Pending, + }, + } + } +} diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 798740234..0ad17ec0a 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -15,9 +15,7 @@ use actix_service::{ ServiceFactoryExt as _, }; use actix_utils::future::ready; -use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; -use h2::server::{handshake as h2_handshake, Handshake as H2Handshake}; use log::error; use crate::{ @@ -28,7 +26,7 @@ use crate::{ ConnectCallback, OnConnectData, Request, Response, }; -use super::dispatcher::Dispatcher; +use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout}; /// `ServiceFactory` implementation for HTTP/2 transport pub struct H2Service { @@ -297,7 +295,7 @@ where Some(self.cfg.clone()), addr, on_connect_data, - h2_handshake(io), + handshake_with_timeout(io, &self.cfg), ), } } @@ -314,7 +312,7 @@ where Option, Option, OnConnectData, - H2Handshake, + HandshakeWithTimeout, ), } @@ -352,7 +350,7 @@ where ref mut on_connect_data, ref mut handshake, ) => match ready!(Pin::new(handshake).poll(cx)) { - Ok(conn) => { + Ok((conn, timer)) => { let on_connect_data = std::mem::take(on_connect_data); self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), @@ -360,12 +358,13 @@ where on_connect_data, config.take().unwrap(), *peer_addr, + timer, )); self.poll(cx) } Err(err) => { trace!("H2 handshake error: {}", err); - Poll::Ready(Err(err.into())) + Poll::Ready(Err(err)) } }, } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index a47dda738..fb0cccb38 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -9,13 +9,11 @@ use std::{ task::{Context, Poll}, }; -use ::h2::server::{handshake as h2_handshake, Handshake as H2Handshake}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{ fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, }; -use bytes::Bytes; use futures_core::{future::LocalBoxFuture, ready}; use pin_project::pin_project; @@ -522,7 +520,7 @@ where match proto { Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake(Some(( - h2_handshake(io), + h2::handshake_with_timeout(io, &self.cfg), self.cfg.clone(), self.flow.clone(), on_connect_data, @@ -567,7 +565,7 @@ where H2(#[pin] h2::Dispatcher), H2Handshake( Option<( - H2Handshake, + h2::HandshakeWithTimeout, ServiceConfig, Rc>, OnConnectData, @@ -625,7 +623,7 @@ where StateProj::H2(disp) => disp.poll(cx), StateProj::H2Handshake(data) => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { - Ok(conn) => { + Ok((conn, timer)) => { let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2( @@ -635,13 +633,14 @@ where on_connect_data, cfg, peer_addr, + timer, ), )); self.poll(cx) } Err(err) => { trace!("H2 handshake error: {}", err); - Poll::Ready(Err(err.into())) + Poll::Ready(Err(err)) } } } diff --git a/actix-http/tests/test_h2_ping_pong.rs b/actix-http/tests/test_h2_ping_pong.rs deleted file mode 100644 index 30ce9aa51..000000000 --- a/actix-http/tests/test_h2_ping_pong.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::io; - -use actix_http::{error::Error, HttpService, Response}; -use actix_server::Server; - -#[actix_rt::test] -async fn h2_ping_pong() -> io::Result<()> { - let (tx, rx) = std::sync::mpsc::sync_channel(1); - - let lst = std::net::TcpListener::bind("127.0.0.1:0")?; - - let addr = lst.local_addr().unwrap(); - - let join = std::thread::spawn(move || { - actix_rt::System::new().block_on(async move { - let srv = Server::build() - .disable_signals() - .workers(1) - .listen("h2_ping_pong", lst, || { - HttpService::build() - .keep_alive(3) - .h2(|_| async { Ok::<_, Error>(Response::ok()) }) - .tcp() - })? - .run(); - - tx.send(srv.handle()).unwrap(); - - srv.await - }) - }); - - let handle = rx.recv().unwrap(); - - let (sync_tx, rx) = std::sync::mpsc::sync_channel(1); - - // use a separate thread for h2 client so it can be blocked. - std::thread::spawn(move || { - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(async move { - let stream = tokio::net::TcpStream::connect(addr).await.unwrap(); - - let (mut tx, conn) = h2::client::handshake(stream).await.unwrap(); - - tokio::spawn(async move { conn.await.unwrap() }); - - let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap(); - let res = res.await.unwrap(); - - assert_eq!(res.status().as_u16(), 200); - - sync_tx.send(()).unwrap(); - - // intentionally block the client thread so it can not answer ping pong. - std::thread::sleep(std::time::Duration::from_secs(1000)); - }) - }); - - rx.recv().unwrap(); - - let now = std::time::Instant::now(); - - // stop server gracefully. this step would take up to 30 seconds. - handle.stop(true).await; - - // join server thread. only when connection are all gone this step would finish. - join.join().unwrap()?; - - // check the time used for join server thread so it's known that the server shutdown - // is from keep alive and not server graceful shutdown timeout. - assert!(now.elapsed() < std::time::Duration::from_secs(30)); - - Ok(()) -} diff --git a/actix-http/tests/test_h2_timer.rs b/actix-http/tests/test_h2_timer.rs new file mode 100644 index 000000000..2b9c26e4a --- /dev/null +++ b/actix-http/tests/test_h2_timer.rs @@ -0,0 +1,153 @@ +use std::io; + +use actix_http::{error::Error, HttpService, Response}; +use actix_server::Server; +use tokio::io::AsyncWriteExt; + +#[actix_rt::test] +async fn h2_ping_pong() -> io::Result<()> { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + + let lst = std::net::TcpListener::bind("127.0.0.1:0")?; + + let addr = lst.local_addr().unwrap(); + + let join = std::thread::spawn(move || { + actix_rt::System::new().block_on(async move { + let srv = Server::build() + .disable_signals() + .workers(1) + .listen("h2_ping_pong", lst, || { + HttpService::build() + .keep_alive(3) + .h2(|_| async { Ok::<_, Error>(Response::ok()) }) + .tcp() + })? + .run(); + + tx.send(srv.handle()).unwrap(); + + srv.await + }) + }); + + let handle = rx.recv().unwrap(); + + let (sync_tx, rx) = std::sync::mpsc::sync_channel(1); + + // use a separate thread for h2 client so it can be blocked. + std::thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + let stream = tokio::net::TcpStream::connect(addr).await.unwrap(); + + let (mut tx, conn) = h2::client::handshake(stream).await.unwrap(); + + tokio::spawn(async move { conn.await.unwrap() }); + + let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap(); + let res = res.await.unwrap(); + + assert_eq!(res.status().as_u16(), 200); + + sync_tx.send(()).unwrap(); + + // intentionally block the client thread so it can not answer ping pong. + std::thread::sleep(std::time::Duration::from_secs(1000)); + }) + }); + + rx.recv().unwrap(); + + let now = std::time::Instant::now(); + + // stop server gracefully. this step would take up to 30 seconds. + handle.stop(true).await; + + // join server thread. only when connection are all gone this step would finish. + join.join().unwrap()?; + + // check the time used for join server thread so it's known that the server shutdown + // is from keep alive and not server graceful shutdown timeout. + assert!(now.elapsed() < std::time::Duration::from_secs(30)); + + Ok(()) +} + +#[actix_rt::test] +async fn h2_handshake_timeout() -> io::Result<()> { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + + let lst = std::net::TcpListener::bind("127.0.0.1:0")?; + + let addr = lst.local_addr().unwrap(); + + let join = std::thread::spawn(move || { + actix_rt::System::new().block_on(async move { + let srv = Server::build() + .disable_signals() + .workers(1) + .listen("h2_ping_pong", lst, || { + HttpService::build() + .keep_alive(30) + // set first request timeout to 5 seconds. + // this is the timeout used for http2 handshake. + .client_timeout(5000) + .h2(|_| async { Ok::<_, Error>(Response::ok()) }) + .tcp() + })? + .run(); + + tx.send(srv.handle()).unwrap(); + + srv.await + }) + }); + + let handle = rx.recv().unwrap(); + + let (sync_tx, rx) = std::sync::mpsc::sync_channel(1); + + // use a separate thread for tcp client so it can be blocked. + std::thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap(); + + // do not send the last new line intentionally. + // This should hang the server handshake + let malicious_buf = b"PRI * HTTP/2.0\r\n\r\nSM\r\n"; + stream.write_all(malicious_buf).await.unwrap(); + stream.flush().await.unwrap(); + + sync_tx.send(()).unwrap(); + + // intentionally block the client thread so it sit idle and not do handshake. + std::thread::sleep(std::time::Duration::from_secs(1000)); + + drop(stream) + }) + }); + + rx.recv().unwrap(); + + let now = std::time::Instant::now(); + + // stop server gracefully. this step would take up to 30 seconds. + handle.stop(true).await; + + // join server thread. only when connection are all gone this step would finish. + join.join().unwrap()?; + + // check the time used for join server thread so it's known that the server shutdown + // is from handshake timeout and not server graceful shutdown timeout. + assert!(now.elapsed() < std::time::Duration::from_secs(30)); + + Ok(()) +} From c7c02ef99d6ef2c600355b1c889eb4f11ffcb88a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 4 Dec 2021 19:40:47 +0000 Subject: [PATCH 137/861] body ergonomics v3 (#2468) --- CHANGES.md | 4 + actix-files/src/chunked.rs | 3 +- actix-files/src/named.rs | 21 +- actix-http/CHANGES.md | 24 +- actix-http/examples/echo2.rs | 8 +- actix-http/src/body/body.rs | 333 --------------------- actix-http/src/body/body_stream.rs | 23 +- actix-http/src/body/boxed.rs | 80 +++++ actix-http/src/body/either.rs | 83 ++++++ actix-http/src/body/message_body.rs | 443 ++++++++++++++++++++-------- actix-http/src/body/mod.rs | 268 +---------------- actix-http/src/body/none.rs | 43 +++ actix-http/src/body/size.rs | 2 +- actix-http/src/body/sized_stream.rs | 2 + actix-http/src/body/utils.rs | 78 +++++ actix-http/src/builder.rs | 20 +- actix-http/src/encoding/encoder.rs | 144 +++++---- actix-http/src/encoding/mod.rs | 3 + actix-http/src/error.rs | 29 +- actix-http/src/h1/dispatcher.rs | 59 ++-- actix-http/src/h1/service.rs | 44 ++- actix-http/src/h1/utils.rs | 38 ++- actix-http/src/h2/dispatcher.rs | 14 +- actix-http/src/h2/service.rs | 28 +- actix-http/src/message.rs | 9 +- actix-http/src/response.rs | 175 ++++++----- actix-http/src/response_builder.rs | 67 ++--- actix-http/src/service.rs | 204 ++++++------- actix-http/src/ws/dispatcher.rs | 26 +- actix-http/src/ws/mod.rs | 33 ++- actix-http/tests/test_client.rs | 4 +- actix-http/tests/test_openssl.rs | 12 +- actix-http/tests/test_rustls.rs | 12 +- actix-http/tests/test_server.rs | 43 +-- actix-http/tests/test_ws.rs | 6 +- actix-test/src/lib.rs | 7 +- actix-web-actors/Cargo.toml | 2 +- actix-web-actors/src/ws.rs | 16 +- actix-web-actors/tests/test_ws.rs | 2 +- awc/Cargo.toml | 1 + awc/src/any_body.rs | 266 +++++++++++++++++ awc/src/client/connection.rs | 8 +- awc/src/client/error.rs | 20 +- awc/src/client/h1proto.rs | 10 +- awc/src/client/h2proto.rs | 16 +- awc/src/connect.rs | 13 +- awc/src/error.rs | 2 + awc/src/frozen.rs | 10 +- awc/src/lib.rs | 3 + awc/src/middleware/redirect.rs | 17 +- awc/src/request.rs | 13 +- awc/src/sender.rs | 24 +- benches/responder.rs | 17 +- src/app.rs | 56 ++-- src/app_service.rs | 28 +- src/data.rs | 2 +- src/dev.rs | 53 +++- src/error/error.rs | 6 +- src/error/internal.rs | 19 +- src/error/macros.rs | 2 +- src/error/response_error.rs | 30 +- src/handler.rs | 43 ++- src/http/header/accept.rs | 2 +- src/lib.rs | 2 + src/middleware/compat.rs | 6 +- src/middleware/compress.rs | 34 +-- src/middleware/logger.rs | 2 +- src/resource.rs | 29 +- src/responder.rs | 299 +++++++++++-------- src/response/builder.rs | 53 ++-- src/response/response.rs | 34 ++- src/route.rs | 23 +- src/scope.rs | 80 ++--- src/server.rs | 10 +- src/service.rs | 42 ++- src/test.rs | 24 +- src/types/either.rs | 10 +- src/types/form.rs | 43 ++- src/types/json.rs | 30 +- src/types/path.rs | 2 +- src/types/payload.rs | 6 +- src/types/query.rs | 2 +- src/web.rs | 8 +- tests/test_server.rs | 7 +- 84 files changed, 2134 insertions(+), 1685 deletions(-) delete mode 100644 actix-http/src/body/body.rs create mode 100644 actix-http/src/body/boxed.rs create mode 100644 actix-http/src/body/either.rs create mode 100644 actix-http/src/body/none.rs create mode 100644 actix-http/src/body/utils.rs create mode 100644 awc/src/any_body.rs diff --git a/CHANGES.md b/CHANGES.md index c754d4dd6..2dc45c3ed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * `AcceptEncoding` typed header. [#2482] +* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -11,8 +13,10 @@ ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] +* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +[#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index fbb46e417..68221ccc3 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -6,8 +6,7 @@ use std::{ task::{Context, Poll}, }; -use actix_web::error::Error; -use bytes::Bytes; +use actix_web::{error::Error, web::Bytes}; use futures_core::{ready, Stream}; use pin_project_lite::pin_project; diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 547048bbd..89775c6b3 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -10,12 +10,12 @@ use std::{ #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use actix_http::body::AnyBody; use actix_service::{Service, ServiceFactory}; use actix_web::{ + body::{self, BoxBody, SizedStream}, dev::{ AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest, - ServiceResponse, SizedStream, + ServiceResponse, }, http::{ header::{ @@ -113,6 +113,8 @@ pub(crate) use std::fs::File; #[cfg(feature = "experimental-io-uring")] pub(crate) use tokio_uring::fs::File; +use super::chunked; + impl NamedFile { /// Creates an instance from a previously opened file. /// @@ -394,7 +396,7 @@ impl NamedFile { } /// Creates an `HttpResponse` with file as a streaming body. - pub fn into_response(self, req: &HttpRequest) -> HttpResponse { + pub fn into_response(self, req: &HttpRequest) -> HttpResponse { if self.status_code != StatusCode::OK { let mut res = HttpResponse::build(self.status_code); @@ -416,7 +418,7 @@ impl NamedFile { res.encoding(current_encoding); } - let reader = super::chunked::new_chunked_read(self.md.len(), 0, self.file); + let reader = chunked::new_chunked_read(self.md.len(), 0, self.file); return res.streaming(reader); } @@ -527,10 +529,13 @@ impl NamedFile { if precondition_failed { return resp.status(StatusCode::PRECONDITION_FAILED).finish(); } else if not_modified { - return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None); + return resp + .status(StatusCode::NOT_MODIFIED) + .body(body::None::new()) + .map_into_boxed_body(); } - let reader = super::chunked::new_chunked_read(length, offset, self.file); + let reader = chunked::new_chunked_read(length, offset, self.file); if offset != 0 || length != self.md.len() { resp.status(StatusCode::PARTIAL_CONTENT); @@ -595,7 +600,9 @@ impl DerefMut for NamedFile { } impl Responder for NamedFile { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = BoxBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { self.into_response(req) } } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1eaccfb2e..23c15296a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,9 +3,31 @@ ## Unreleased - 2021-xx-xx ### Added * Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +* `Response::map_into_boxed_body`. [#2468] +* `body::EitherBody` enum. [#2468] +* `body::None` struct. [#2468] +* Impl `MessageBody` for `bytestring::ByteString`. [#2468] +* `impl Clone for ws::HandshakeError`. [#2468] + +### Changed +* Rename `body::BoxBody::{from_body => new}`. [#2468] +* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +* Error types using in service builders now require `Into>`. [#2468] +* `From` implementations on error types now return a `Response`. [#2468] +* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +* `ResponseBuilder::finish()` now returns `Response>`. [#2468] + +### Removed +* `ResponseBuilder::streaming`. [#2468] +* `impl Future` for `ResponseBuilder`. [#2468] +* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] [#2483]: https://github.com/actix/actix-web/pull/2483 +[#2468]: https://github.com/actix/actix-web/pull/2468 + ## 3.0.0-beta.14 - 2021-11-30 ### Changed diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 6e5ddec7c..6092c01ce 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,12 +1,14 @@ use std::io; -use actix_http::{body::AnyBody, http::HeaderValue, http::StatusCode}; -use actix_http::{Error, HttpService, Request, Response}; +use actix_http::{ + body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request, + Response, +}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; -async fn handle_request(mut req: Request) -> Result, Error> { +async fn handle_request(mut req: Request) -> Result, Error> { let mut body = BytesMut::new(); while let Some(item) = req.payload().next().await { body.extend_from_slice(&item?) diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs deleted file mode 100644 index e8861024b..000000000 --- a/actix-http/src/body/body.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::{ - borrow::Cow, - error::Error as StdError, - fmt, mem, - pin::Pin, - task::{Context, Poll}, -}; - -use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use pin_project::pin_project; - -use crate::error::Error; - -use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream}; - -#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")] -pub type Body = AnyBody; - -/// Represents various types of HTTP message body. -#[pin_project(project = AnyBodyProj)] -#[derive(Clone)] -pub enum AnyBody { - /// Empty response. `Content-Length` header is not set. - None, - - /// Complete, in-memory response body. - Bytes(Bytes), - - /// Generic / Other message body. - Body(#[pin] B), -} - -impl AnyBody { - /// Constructs a "body" representing an empty response. - pub fn none() -> Self { - Self::None - } - - /// Constructs a new, 0-length body. - pub fn empty() -> Self { - Self::Bytes(Bytes::new()) - } - - /// Create boxed body from generic message body. - pub fn new_boxed(body: B) -> Self - where - B: MessageBody + 'static, - B::Error: Into>, - { - Self::Body(BoxBody::from_body(body)) - } - - /// Constructs new `AnyBody` instance from a slice of bytes by copying it. - /// - /// If your bytes container is owned, it may be cheaper to use a `From` impl. - pub fn copy_from_slice(s: &[u8]) -> Self { - Self::Bytes(Bytes::copy_from_slice(s)) - } - - #[doc(hidden)] - #[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")] - pub fn from_slice(s: &[u8]) -> Self { - Self::Bytes(Bytes::copy_from_slice(s)) - } -} - -impl AnyBody -where - B: MessageBody + 'static, - B::Error: Into>, -{ - /// Create body from generic message body. - pub fn new(body: B) -> Self { - Self::Body(body) - } - - pub fn into_boxed(self) -> AnyBody { - match self { - Self::None => AnyBody::None, - Self::Bytes(bytes) => AnyBody::Bytes(bytes), - Self::Body(body) => AnyBody::new_boxed(body), - } - } -} - -impl MessageBody for AnyBody -where - B: MessageBody, - B::Error: Into> + 'static, -{ - type Error = Error; - - fn size(&self) -> BodySize { - match self { - AnyBody::None => BodySize::None, - AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - AnyBody::Body(ref body) => body.size(), - } - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - match self.project() { - AnyBodyProj::None => Poll::Ready(None), - AnyBodyProj::Bytes(bin) => { - let len = bin.len(); - if len == 0 { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(bin)))) - } - } - - AnyBodyProj::Body(body) => body - .poll_next(cx) - .map_err(|err| Error::new_body().with_cause(err)), - } - } -} - -impl PartialEq for AnyBody { - fn eq(&self, other: &AnyBody) -> bool { - match *self { - AnyBody::None => matches!(*other, AnyBody::None), - AnyBody::Bytes(ref b) => match *other { - AnyBody::Bytes(ref b2) => b == b2, - _ => false, - }, - AnyBody::Body(_) => false, - } - } -} - -impl fmt::Debug for AnyBody { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - AnyBody::None => write!(f, "AnyBody::None"), - AnyBody::Bytes(ref bytes) => write!(f, "AnyBody::Bytes({:?})", bytes), - AnyBody::Body(ref stream) => write!(f, "AnyBody::Message({:?})", stream), - } - } -} - -impl From<&'static str> for AnyBody { - fn from(string: &'static str) -> Self { - Self::Bytes(Bytes::from_static(string.as_ref())) - } -} - -impl From<&'static [u8]> for AnyBody { - fn from(bytes: &'static [u8]) -> Self { - Self::Bytes(Bytes::from_static(bytes)) - } -} - -impl From> for AnyBody { - fn from(vec: Vec) -> Self { - Self::Bytes(Bytes::from(vec)) - } -} - -impl From for AnyBody { - fn from(string: String) -> Self { - Self::Bytes(Bytes::from(string)) - } -} - -impl From<&'_ String> for AnyBody { - fn from(string: &String) -> Self { - Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string))) - } -} - -impl From> for AnyBody { - fn from(string: Cow<'_, str>) -> Self { - match string { - Cow::Owned(s) => Self::from(s), - Cow::Borrowed(s) => { - Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s))) - } - } - } -} - -impl From for AnyBody { - fn from(bytes: Bytes) -> Self { - Self::Bytes(bytes) - } -} - -impl From for AnyBody { - fn from(bytes: BytesMut) -> Self { - Self::Bytes(bytes.freeze()) - } -} - -impl From> for AnyBody> -where - S: Stream> + 'static, - E: Into> + 'static, -{ - fn from(stream: SizedStream) -> Self { - AnyBody::new(stream) - } -} - -impl From> for AnyBody -where - S: Stream> + 'static, - E: Into> + 'static, -{ - fn from(stream: SizedStream) -> Self { - AnyBody::new_boxed(stream) - } -} - -impl From> for AnyBody> -where - S: Stream> + 'static, - E: Into> + 'static, -{ - fn from(stream: BodyStream) -> Self { - AnyBody::new(stream) - } -} - -impl From> for AnyBody -where - S: Stream> + 'static, - E: Into> + 'static, -{ - fn from(stream: BodyStream) -> Self { - AnyBody::new_boxed(stream) - } -} - -/// A boxed message body with boxed errors. -pub struct BoxBody(Pin>>>); - -impl BoxBody { - /// Boxes a `MessageBody` and any errors it generates. - pub fn from_body(body: B) -> Self - where - B: MessageBody + 'static, - B::Error: Into>, - { - let body = MessageBodyMapErr::new(body, Into::into); - Self(Box::pin(body)) - } - - /// Returns a mutable pinned reference to the inner message body type. - pub fn as_pin_mut( - &mut self, - ) -> Pin<&mut (dyn MessageBody>)> { - self.0.as_mut() - } -} - -impl fmt::Debug for BoxBody { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("BoxAnyBody(dyn MessageBody)") - } -} - -impl MessageBody for BoxBody { - type Error = Error; - - fn size(&self) -> BodySize { - self.0.size() - } - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - self.0 - .as_mut() - .poll_next(cx) - .map_err(|err| Error::new_body().with_cause(err)) - } -} - -#[cfg(test)] -mod tests { - use std::marker::PhantomPinned; - - use static_assertions::{assert_impl_all, assert_not_impl_all}; - - use super::*; - use crate::body::to_bytes; - - struct PinType(PhantomPinned); - - impl MessageBody for PinType { - type Error = crate::Error; - - fn size(&self) -> BodySize { - unimplemented!() - } - - fn poll_next( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll>> { - unimplemented!() - } - } - - assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody>: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin); - assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin); - assert_impl_all!(AnyBody: MessageBody); - - assert_not_impl_all!(AnyBody: Send, Sync, Unpin); - assert_not_impl_all!(BoxBody: Send, Sync, Unpin); - assert_not_impl_all!(AnyBody: Send, Sync, Unpin); - - #[actix_rt::test] - async fn nested_boxed_body() { - let body = AnyBody::copy_from_slice(&[1, 2, 3]); - let boxed_body = BoxBody::from_body(BoxBody::from_body(body)); - - assert_eq!( - to_bytes(boxed_body).await.unwrap(), - Bytes::from(vec![1, 2, 3]), - ); - } -} diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 31de9b48f..1da7a848a 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -20,6 +20,8 @@ pin_project! { } } +// TODO: from_infallible method + impl BodyStream where S: Stream>, @@ -75,6 +77,7 @@ mod tests { use derive_more::{Display, Error}; use futures_core::ready; use futures_util::{stream, FutureExt as _}; + use pin_project_lite::pin_project; use static_assertions::{assert_impl_all, assert_not_impl_all}; use super::*; @@ -166,12 +169,14 @@ mod tests { BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)])); assert!(matches!(to_bytes(body).await, Err(StreamErr))); - #[pin_project::pin_project(project = TimeDelayStreamProj)] - #[derive(Debug)] - enum TimeDelayStream { - Start, - Sleep(Pin>), - Done, + pin_project! { + #[derive(Debug)] + #[project = TimeDelayStreamProj] + enum TimeDelayStream { + Start, + Sleep { delay: Pin> }, + Done, + } } impl Stream for TimeDelayStream { @@ -184,12 +189,14 @@ mod tests { match self.as_mut().get_mut() { TimeDelayStream::Start => { let sleep = sleep(Duration::from_millis(1)); - self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep))); + self.as_mut().set(TimeDelayStream::Sleep { + delay: Box::pin(sleep), + }); cx.waker().wake_by_ref(); Poll::Pending } - TimeDelayStream::Sleep(ref mut delay) => { + TimeDelayStream::Sleep { ref mut delay } => { ready!(delay.poll_unpin(cx)); self.set(TimeDelayStream::Done); cx.waker().wake_by_ref(); diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs new file mode 100644 index 000000000..9442bd1df --- /dev/null +++ b/actix-http/src/body/boxed.rs @@ -0,0 +1,80 @@ +use std::{ + error::Error as StdError, + fmt, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; + +use super::{BodySize, MessageBody, MessageBodyMapErr}; +use crate::Error; + +/// A boxed message body with boxed errors. +pub struct BoxBody(Pin>>>); + +impl BoxBody { + /// Boxes a `MessageBody` and any errors it generates. + pub fn new(body: B) -> Self + where + B: MessageBody + 'static, + { + let body = MessageBodyMapErr::new(body, Into::into); + Self(Box::pin(body)) + } + + /// Returns a mutable pinned reference to the inner message body type. + pub fn as_pin_mut( + &mut self, + ) -> Pin<&mut (dyn MessageBody>)> { + self.0.as_mut() + } +} + +impl fmt::Debug for BoxBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BoxBody(dyn MessageBody)") + } +} + +impl MessageBody for BoxBody { + type Error = Error; + + fn size(&self) -> BodySize { + self.0.size() + } + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + self.0 + .as_mut() + .poll_next(cx) + .map_err(|err| Error::new_body().with_cause(err)) + } +} + +#[cfg(test)] +mod tests { + + use static_assertions::{assert_impl_all, assert_not_impl_all}; + + use super::*; + use crate::body::to_bytes; + + assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin); + + assert_not_impl_all!(BoxBody: Send, Sync, Unpin); + + #[actix_rt::test] + async fn nested_boxed_body() { + let body = Bytes::from_static(&[1, 2, 3]); + let boxed_body = BoxBody::new(BoxBody::new(body)); + + assert_eq!( + to_bytes(boxed_body).await.unwrap(), + Bytes::from(vec![1, 2, 3]), + ); + } +} diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs new file mode 100644 index 000000000..6169ee627 --- /dev/null +++ b/actix-http/src/body/either.rs @@ -0,0 +1,83 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use pin_project_lite::pin_project; + +use super::{BodySize, BoxBody, MessageBody}; +use crate::Error; + +pin_project! { + #[project = EitherBodyProj] + #[derive(Debug, Clone)] + pub enum EitherBody { + /// A body of type `L`. + Left { #[pin] body: L }, + + /// A body of type `R`. + Right { #[pin] body: R }, + } +} + +impl EitherBody { + /// Creates new `EitherBody` using left variant and boxed right variant. + pub fn new(body: L) -> Self { + Self::Left { body } + } +} + +impl EitherBody { + /// Creates new `EitherBody` using left variant. + pub fn left(body: L) -> Self { + Self::Left { body } + } + + /// Creates new `EitherBody` using right variant. + pub fn right(body: R) -> Self { + Self::Right { body } + } +} + +impl MessageBody for EitherBody +where + L: MessageBody + 'static, + R: MessageBody + 'static, +{ + type Error = Error; + + fn size(&self) -> BodySize { + match self { + EitherBody::Left { body } => body.size(), + EitherBody::Right { body } => body.size(), + } + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.project() { + EitherBodyProj::Left { body } => body + .poll_next(cx) + .map_err(|err| Error::new_body().with_cause(err)), + EitherBodyProj::Right { body } => body + .poll_next(cx) + .map_err(|err| Error::new_body().with_cause(err)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn type_parameter_inference() { + let _body: EitherBody<(), _> = EitherBody::new(()); + + let _body: EitherBody<_, ()> = EitherBody::left(()); + let _body: EitherBody<(), _> = EitherBody::right(()); + } +} diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 62a7e9b1c..053b6f286 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -2,6 +2,7 @@ use std::{ convert::Infallible, + error::Error as StdError, mem, pin::Pin, task::{Context, Poll}, @@ -13,9 +14,12 @@ use pin_project_lite::pin_project; use super::BodySize; -/// An interface for response bodies. +/// An interface types that can converted to bytes and used as response bodies. +// TODO: examples pub trait MessageBody { - type Error; + // TODO: consider this bound to only fmt::Display since the error type is not really used + // and there is an impl for Into> on String + type Error: Into>; /// Body size hint. fn size(&self) -> BodySize; @@ -27,152 +31,218 @@ pub trait MessageBody { ) -> Poll>>; } -impl MessageBody for () { - type Error = Infallible; +mod foreign_impls { + use super::*; - fn size(&self) -> BodySize { - BodySize::Sized(0) - } + impl MessageBody for Infallible { + type Error = Infallible; - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - Poll::Ready(None) - } -} + #[inline] + fn size(&self) -> BodySize { + match *self {} + } -impl MessageBody for Box -where - B: MessageBody + Unpin, -{ - type Error = B::Error; - - fn size(&self) -> BodySize { - self.as_ref().size() - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - Pin::new(self.get_mut().as_mut()).poll_next(cx) - } -} - -impl MessageBody for Pin> -where - B: MessageBody, -{ - type Error = B::Error; - - fn size(&self) -> BodySize { - self.as_ref().size() - } - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - self.as_mut().poll_next(cx) - } -} - -impl MessageBody for Bytes { - type Error = Infallible; - - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(self.get_mut())))) + #[inline] + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + match *self {} } } -} -impl MessageBody for BytesMut { - type Error = Infallible; + impl MessageBody for () { + type Error = Infallible; - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } + #[inline] + fn size(&self) -> BodySize { + BodySize::Sized(0) + } - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { + #[inline] + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze()))) } } -} -impl MessageBody for &'static str { - type Error = Infallible; + impl MessageBody for Box + where + B: MessageBody + Unpin, + { + type Error = B::Error; - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } + #[inline] + fn size(&self) -> BodySize { + self.as_ref().size() + } - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from_static( - mem::take(self.get_mut()).as_ref(), - )))) + #[inline] + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + Pin::new(self.get_mut().as_mut()).poll_next(cx) } } -} -impl MessageBody for Vec { - type Error = Infallible; + impl MessageBody for Pin> + where + B: MessageBody, + { + type Error = B::Error; - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } + #[inline] + fn size(&self) -> BodySize { + self.as_ref().size() + } - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut()))))) + #[inline] + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + self.as_mut().poll_next(cx) } } -} -impl MessageBody for String { - type Error = Infallible; + impl MessageBody for &'static [u8] { + type Error = Infallible; - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let bytes = mem::take(self.get_mut()); + let bytes = Bytes::from_static(bytes); + Poll::Ready(Some(Ok(bytes))) + } + } } - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from( - mem::take(self.get_mut()).into_bytes(), - )))) + impl MessageBody for Bytes { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let bytes = mem::take(self.get_mut()); + Poll::Ready(Some(Ok(bytes))) + } + } + } + + impl MessageBody for BytesMut { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let bytes = mem::take(self.get_mut()).freeze(); + Poll::Ready(Some(Ok(bytes))) + } + } + } + + impl MessageBody for Vec { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let bytes = mem::take(self.get_mut()); + Poll::Ready(Some(Ok(Bytes::from(bytes)))) + } + } + } + + impl MessageBody for &'static str { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let string = mem::take(self.get_mut()); + let bytes = Bytes::from_static(string.as_bytes()); + Poll::Ready(Some(Ok(bytes))) + } + } + } + + impl MessageBody for String { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + let string = mem::take(self.get_mut()); + Poll::Ready(Some(Ok(Bytes::from(string)))) + } + } + } + + impl MessageBody for bytestring::ByteString { + type Error = Infallible; + + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + let string = mem::take(self.get_mut()); + Poll::Ready(Some(Ok(string.into_bytes()))) } } } @@ -202,6 +272,7 @@ impl MessageBody for MessageBodyMapErr where B: MessageBody, F: FnOnce(B::Error) -> E, + E: Into>, { type Error = E; @@ -226,3 +297,129 @@ where } } } + +#[cfg(test)] +mod tests { + use actix_rt::pin; + use actix_utils::future::poll_fn; + use bytes::{Bytes, BytesMut}; + + use super::*; + + macro_rules! assert_poll_next { + ($pin:expr, $exp:expr) => { + assert_eq!( + poll_fn(|cx| $pin.as_mut().poll_next(cx)) + .await + .unwrap() // unwrap option + .unwrap(), // unwrap result + $exp + ); + }; + } + + macro_rules! assert_poll_next_none { + ($pin:expr) => { + assert!(poll_fn(|cx| $pin.as_mut().poll_next(cx)).await.is_none()); + }; + } + + #[actix_rt::test] + async fn boxing_equivalence() { + assert_eq!(().size(), BodySize::Sized(0)); + assert_eq!(().size(), Box::new(()).size()); + assert_eq!(().size(), Box::pin(()).size()); + + let pl = Box::new(()); + pin!(pl); + assert_poll_next_none!(pl); + + let mut pl = Box::pin(()); + assert_poll_next_none!(pl); + } + + #[actix_rt::test] + async fn test_unit() { + let pl = (); + assert_eq!(pl.size(), BodySize::Sized(0)); + pin!(pl); + assert_poll_next_none!(pl); + } + + #[actix_rt::test] + async fn test_static_str() { + assert_eq!("".size(), BodySize::Sized(0)); + assert_eq!("test".size(), BodySize::Sized(4)); + + let pl = "test"; + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + #[actix_rt::test] + async fn test_static_bytes() { + assert_eq!(b"".as_ref().size(), BodySize::Sized(0)); + assert_eq!(b"test".as_ref().size(), BodySize::Sized(4)); + + let pl = b"test".as_ref(); + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + #[actix_rt::test] + async fn test_vec() { + assert_eq!(vec![0; 0].size(), BodySize::Sized(0)); + assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); + + let pl = Vec::from("test"); + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + #[actix_rt::test] + async fn test_bytes() { + assert_eq!(Bytes::new().size(), BodySize::Sized(0)); + assert_eq!(Bytes::from_static(b"test").size(), BodySize::Sized(4)); + + let pl = Bytes::from_static(b"test"); + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + #[actix_rt::test] + async fn test_bytes_mut() { + assert_eq!(BytesMut::new().size(), BodySize::Sized(0)); + assert_eq!(BytesMut::from(b"test".as_ref()).size(), BodySize::Sized(4)); + + let pl = BytesMut::from("test"); + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + #[actix_rt::test] + async fn test_string() { + assert_eq!(String::new().size(), BodySize::Sized(0)); + assert_eq!("test".to_owned().size(), BodySize::Sized(4)); + + let pl = "test".to_owned(); + pin!(pl); + assert_poll_next!(pl, Bytes::from("test")); + } + + // down-casting used to be done with a method on MessageBody trait + // test is kept to demonstrate equivalence of Any trait + #[actix_rt::test] + async fn test_body_casting() { + let mut body = String::from("hello cast"); + // let mut resp_body: &mut dyn MessageBody = &mut body; + let resp_body: &mut dyn std::any::Any = &mut body; + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast"); + let body = &mut resp_body.downcast_mut::().unwrap(); + body.push('!'); + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast!"); + let not_body = resp_body.downcast_ref::<()>(); + assert!(not_body.is_none()); + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index df6c6b08a..af7c4626f 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,272 +1,20 @@ //! Traits and structures to aid consuming and writing HTTP payloads. -use std::task::Poll; - -use actix_rt::pin; -use actix_utils::future::poll_fn; -use bytes::{Bytes, BytesMut}; -use futures_core::ready; - -#[allow(clippy::module_inception)] -mod body; mod body_stream; +mod boxed; +mod either; mod message_body; +mod none; mod size; mod sized_stream; +mod utils; -#[allow(deprecated)] -pub use self::body::{AnyBody, Body, BoxBody}; pub use self::body_stream::BodyStream; +pub use self::boxed::BoxBody; +pub use self::either::EitherBody; pub use self::message_body::MessageBody; pub(crate) use self::message_body::MessageBodyMapErr; +pub use self::none::None; pub use self::size::BodySize; pub use self::sized_stream::SizedStream; - -/// Collects the body produced by a `MessageBody` implementation into `Bytes`. -/// -/// Any errors produced by the body stream are returned immediately. -/// -/// # Examples -/// ``` -/// use actix_http::body::{AnyBody, to_bytes}; -/// use bytes::Bytes; -/// -/// # async fn test_to_bytes() { -/// let body = AnyBody::none(); -/// let bytes = to_bytes(body).await.unwrap(); -/// assert!(bytes.is_empty()); -/// -/// let body = AnyBody::copy_from_slice(b"123"); -/// let bytes = to_bytes(body).await.unwrap(); -/// assert_eq!(bytes, b"123"[..]); -/// # } -/// ``` -pub async fn to_bytes(body: B) -> Result { - let cap = match body.size() { - BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()), - BodySize::Sized(size) => size as usize, - // good enough first guess for chunk size - BodySize::Stream => 32_768, - }; - - let mut buf = BytesMut::with_capacity(cap); - - pin!(body); - - poll_fn(|cx| loop { - let body = body.as_mut(); - - match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), - None => return Poll::Ready(Ok(())), - Some(Err(err)) => return Poll::Ready(Err(err)), - } - }) - .await?; - - Ok(buf.freeze()) -} - -#[cfg(test)] -mod tests { - use std::pin::Pin; - - use actix_rt::pin; - use actix_utils::future::poll_fn; - use bytes::{Bytes, BytesMut}; - - use super::{to_bytes, AnyBody as TestAnyBody, BodySize, MessageBody as _}; - - impl AnyBody { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - AnyBody::Bytes(ref bin) => bin, - _ => panic!(), - } - } - } - - /// AnyBody alias because rustc does not (can not?) infer the default type parameter. - type AnyBody = TestAnyBody; - - #[actix_rt::test] - async fn test_static_str() { - assert_eq!(AnyBody::from("").size(), BodySize::Sized(0)); - assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from("test").get_ref(), b"test"); - - assert_eq!("test".size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| Pin::new(&mut "test").poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_static_bytes() { - assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test"); - assert_eq!( - AnyBody::copy_from_slice(b"test".as_ref()).size(), - BodySize::Sized(4) - ); - assert_eq!( - AnyBody::copy_from_slice(b"test".as_ref()).get_ref(), - b"test" - ); - let sb = Bytes::from(&b"test"[..]); - pin!(sb); - - assert_eq!(sb.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_vec() { - assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test"); - let test_vec = Vec::from("test"); - pin!(test_vec); - - assert_eq!(test_vec.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| test_vec.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes() { - let b = Bytes::from("test"); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); - pin!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes_mut() { - let b = BytesMut::from("test"); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); - pin!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_string() { - let b = "test".to_owned(); - assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); - assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4)); - assert_eq!(AnyBody::from(&b).get_ref(), b"test"); - pin!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_unit() { - assert_eq!(().size(), BodySize::Sized(0)); - assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) - .await - .is_none()); - } - - #[actix_rt::test] - async fn test_box_and_pin() { - let val = Box::new(()); - pin!(val); - assert_eq!(val.size(), BodySize::Sized(0)); - assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); - - let mut val = Box::pin(()); - assert_eq!(val.size(), BodySize::Sized(0)); - assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); - } - - #[actix_rt::test] - async fn test_body_eq() { - assert!( - AnyBody::Bytes(Bytes::from_static(b"1")) - == AnyBody::Bytes(Bytes::from_static(b"1")) - ); - assert!(AnyBody::Bytes(Bytes::from_static(b"1")) != AnyBody::None); - } - - #[actix_rt::test] - async fn test_body_debug() { - assert!(format!("{:?}", AnyBody::None).contains("Body::None")); - assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1')); - } - - #[actix_rt::test] - async fn test_serde_json() { - use serde_json::{json, Value}; - assert_eq!( - AnyBody::from( - serde_json::to_vec(&Value::String("test".to_owned())).unwrap() - ) - .size(), - BodySize::Sized(6) - ); - assert_eq!( - AnyBody::from( - serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap() - ) - .size(), - BodySize::Sized(25) - ); - } - - // down-casting used to be done with a method on MessageBody trait - // test is kept to demonstrate equivalence of Any trait - #[actix_rt::test] - async fn test_body_casting() { - let mut body = String::from("hello cast"); - // let mut resp_body: &mut dyn MessageBody = &mut body; - let resp_body: &mut dyn std::any::Any = &mut body; - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast"); - let body = &mut resp_body.downcast_mut::().unwrap(); - body.push('!'); - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast!"); - let not_body = resp_body.downcast_ref::<()>(); - assert!(not_body.is_none()); - } - - #[actix_rt::test] - async fn test_to_bytes() { - let body = AnyBody::empty(); - let bytes = to_bytes(body).await.unwrap(); - assert!(bytes.is_empty()); - - let body = AnyBody::copy_from_slice(b"123"); - let bytes = to_bytes(body).await.unwrap(); - assert_eq!(bytes, b"123"[..]); - } -} +pub use self::utils::to_bytes; diff --git a/actix-http/src/body/none.rs b/actix-http/src/body/none.rs new file mode 100644 index 000000000..0fc7c8c9f --- /dev/null +++ b/actix-http/src/body/none.rs @@ -0,0 +1,43 @@ +use std::{ + convert::Infallible, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; + +use super::{BodySize, MessageBody}; + +/// Body type for responses that forbid payloads. +/// +/// Distinct from an empty response which would contain a Content-Length header. +/// +/// For an "empty" body, use `()` or `Bytes::new()`. +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct None; + +impl None { + /// Constructs new "none" body. + #[inline] + pub fn new() -> Self { + None + } +} + +impl MessageBody for None { + type Error = Infallible; + + #[inline] + fn size(&self) -> BodySize { + BodySize::None + } + + #[inline] + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + Poll::Ready(Option::None) + } +} diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs index e238eadac..d64af9d44 100644 --- a/actix-http/src/body/size.rs +++ b/actix-http/src/body/size.rs @@ -18,7 +18,7 @@ pub enum BodySize { } impl BodySize { - /// Returns true if size hint indicates no or empty body. + /// Returns true if size hint indicates omitted or empty body. /// /// Streams will return false because it cannot be known without reading the stream. /// diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index b92de44cc..c8606897d 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -32,6 +32,8 @@ where } } +// TODO: from_infallible method + impl MessageBody for SizedStream where S: Stream>, diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs new file mode 100644 index 000000000..a421ffd76 --- /dev/null +++ b/actix-http/src/body/utils.rs @@ -0,0 +1,78 @@ +use std::task::Poll; + +use actix_rt::pin; +use actix_utils::future::poll_fn; +use bytes::{Bytes, BytesMut}; +use futures_core::ready; + +use super::{BodySize, MessageBody}; + +/// Collects the body produced by a `MessageBody` implementation into `Bytes`. +/// +/// Any errors produced by the body stream are returned immediately. +/// +/// # Examples +/// ``` +/// use actix_http::body::{self, to_bytes}; +/// use bytes::Bytes; +/// +/// # async fn test_to_bytes() { +/// let body = body::None::new(); +/// let bytes = to_bytes(body).await.unwrap(); +/// assert!(bytes.is_empty()); +/// +/// let body = Bytes::from_static(b"123"); +/// let bytes = to_bytes(body).await.unwrap(); +/// assert_eq!(bytes, b"123"[..]); +/// # } +/// ``` +pub async fn to_bytes(body: B) -> Result { + let cap = match body.size() { + BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()), + BodySize::Sized(size) => size as usize, + // good enough first guess for chunk size + BodySize::Stream => 32_768, + }; + + let mut buf = BytesMut::with_capacity(cap); + + pin!(body); + + poll_fn(|cx| loop { + let body = body.as_mut(); + + match ready!(body.poll_next(cx)) { + Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), + None => return Poll::Ready(Ok(())), + Some(Err(err)) => return Poll::Ready(Err(err)), + } + }) + .await?; + + Ok(buf.freeze()) +} + +#[cfg(test)] +mod test { + use futures_util::{stream, StreamExt as _}; + + use super::*; + use crate::{body::BodyStream, Error}; + + #[actix_rt::test] + async fn test_to_bytes() { + let bytes = to_bytes(()).await.unwrap(); + assert!(bytes.is_empty()); + + let body = Bytes::from_static(b"123"); + let bytes = to_bytes(body).await.unwrap(); + assert_eq!(bytes, b"123"[..]); + + let stream = + stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")]) + .map(Ok::<_, Error>); + let body = BodyStream::new(stream); + let bytes = to_bytes(body).await.unwrap(); + assert_eq!(bytes, b"123abc"[..]); + } +} diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 4e68dc920..ca821f1d9 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,10 +1,10 @@ -use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc}; +use std::{fmt, marker::PhantomData, net, rc::Rc}; use actix_codec::Framed; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, MessageBody}, config::{KeepAlive, ServiceConfig}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, h2::H2Service, @@ -31,7 +31,7 @@ pub struct HttpServiceBuilder { impl HttpServiceBuilder where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, { @@ -54,11 +54,11 @@ where impl HttpServiceBuilder where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, X: ServiceFactory, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, @@ -120,7 +120,7 @@ where where F: IntoServiceFactory, X1: ServiceFactory, - X1::Error: Into>, + X1::Error: Into>, X1::InitError: fmt::Debug, { HttpServiceBuilder { @@ -178,7 +178,7 @@ where where B: MessageBody, F: IntoServiceFactory, - S::Error: Into>, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, { @@ -200,12 +200,11 @@ where pub fn h2(self, service: F) -> H2Service where F: IntoServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into>, { let cfg = ServiceConfig::new( self.keep_alive, @@ -223,12 +222,11 @@ where pub fn finish(self, service: F) -> HttpService where F: IntoServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into>, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 62100ff1d..49e5663dc 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -12,7 +12,7 @@ use actix_rt::task::{spawn_blocking, JoinHandle}; use bytes::Bytes; use derive_more::Display; use futures_core::ready; -use pin_project::pin_project; +use pin_project_lite::pin_project; #[cfg(feature = "compress-brotli")] use brotli2::write::BrotliEncoder; @@ -23,8 +23,10 @@ use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "compress-zstd")] use zstd::stream::write::Encoder as ZstdEncoder; +use super::Writer; use crate::{ - body::{AnyBody, BodySize, MessageBody}, + body::{BodySize, MessageBody}, + error::BlockingError, http::{ header::{ContentEncoding, CONTENT_ENCODING}, HeaderValue, StatusCode, @@ -32,84 +34,92 @@ use crate::{ ResponseHead, }; -use super::Writer; -use crate::error::BlockingError; - const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024; -#[pin_project] -pub struct Encoder { - eof: bool, - #[pin] - body: EncoderBody, - encoder: Option, - fut: Option>>, +pin_project! { + pub struct Encoder { + #[pin] + body: EncoderBody, + encoder: Option, + fut: Option>>, + eof: bool, + } } impl Encoder { + fn none() -> Self { + Encoder { + body: EncoderBody::None, + encoder: None, + fut: None, + eof: true, + } + } + pub fn response( encoding: ContentEncoding, head: &mut ResponseHead, - body: AnyBody, - ) -> AnyBody> { + body: B, + ) -> Self { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT || encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto); - let body = match body { - AnyBody::None => return AnyBody::None, - AnyBody::Bytes(buf) => { - if can_encode { - EncoderBody::Bytes(buf) - } else { - return AnyBody::Bytes(buf); - } - } - AnyBody::Body(body) => EncoderBody::Stream(body), - }; + match body.size() { + // no need to compress an empty body + BodySize::None => return Self::none(), + + // we cannot assume that Sized is not a stream + BodySize::Sized(_) | BodySize::Stream => {} + } + + // TODO potentially some optimisation for single-chunk responses here by trying to read the + // payload eagerly, stopping after 2 polls if the first is a chunk and the second is None if can_encode { - // Modify response body only if encoder is not None + // Modify response body only if encoder is set if let Some(enc) = ContentEncoder::encoder(encoding) { update_head(encoding, head); head.no_chunking(false); - return AnyBody::Body(Encoder { - body, - eof: false, - fut: None, + return Encoder { + body: EncoderBody::Stream { body }, encoder: Some(enc), - }); + fut: None, + eof: false, + }; } } - AnyBody::Body(Encoder { - body, - eof: false, - fut: None, + Encoder { + body: EncoderBody::Stream { body }, encoder: None, - }) + fut: None, + eof: false, + } } } -#[pin_project(project = EncoderBodyProj)] -enum EncoderBody { - Bytes(Bytes), - Stream(#[pin] B), +pin_project! { + #[project = EncoderBodyProj] + enum EncoderBody { + None, + Stream { #[pin] body: B }, + } } impl MessageBody for EncoderBody where B: MessageBody, { - type Error = EncoderError; + type Error = EncoderError; fn size(&self) -> BodySize { match self { - EncoderBody::Bytes(ref b) => b.size(), - EncoderBody::Stream(ref b) => b.size(), + EncoderBody::None => BodySize::None, + EncoderBody::Stream { body } => body.size(), } } @@ -118,14 +128,11 @@ where cx: &mut Context<'_>, ) -> Poll>> { match self.project() { - EncoderBodyProj::Bytes(b) => { - if b.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(std::mem::take(b)))) - } - } - EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body), + EncoderBodyProj::None => Poll::Ready(None), + + EncoderBodyProj::Stream { body } => body + .poll_next(cx) + .map_err(|err| EncoderError::Body(err.into())), } } } @@ -134,7 +141,7 @@ impl MessageBody for Encoder where B: MessageBody, { - type Error = EncoderError; + type Error = EncoderError; fn size(&self) -> BodySize { if self.encoder.is_none() { @@ -197,6 +204,7 @@ where None => { if let Some(encoder) = this.encoder.take() { let chunk = encoder.finish().map_err(EncoderError::Io)?; + if chunk.is_empty() { return Poll::Ready(None); } else { @@ -222,12 +230,15 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { enum ContentEncoder { #[cfg(feature = "compress-gzip")] Deflate(ZlibEncoder), + #[cfg(feature = "compress-gzip")] Gzip(GzEncoder), + #[cfg(feature = "compress-brotli")] Br(BrotliEncoder), - // We need explicit 'static lifetime here because ZstdEncoder need lifetime - // argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static` + + // Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we + // use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`. #[cfg(feature = "compress-zstd")] Zstd(ZstdEncoder<'static, Writer>), } @@ -240,20 +251,24 @@ impl ContentEncoder { Writer::new(), flate2::Compression::fast(), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( Writer::new(), flate2::Compression::fast(), ))), + #[cfg(feature = "compress-brotli")] ContentEncoding::Br => { Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) } + #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => { let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?; Some(ContentEncoder::Zstd(encoder)) } + _ => None, } } @@ -263,10 +278,13 @@ impl ContentEncoder { match *self { #[cfg(feature = "compress-brotli")] ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(), } @@ -279,16 +297,19 @@ impl ContentEncoder { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), @@ -307,6 +328,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), @@ -315,6 +337,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), @@ -323,6 +346,7 @@ impl ContentEncoder { Err(err) } }, + #[cfg(feature = "compress-zstd")] ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), @@ -337,9 +361,9 @@ impl ContentEncoder { #[derive(Debug, Display)] #[non_exhaustive] -pub enum EncoderError { +pub enum EncoderError { #[display(fmt = "body")] - Body(E), + Body(Box), #[display(fmt = "blocking")] Blocking(BlockingError), @@ -348,18 +372,18 @@ pub enum EncoderError { Io(io::Error), } -impl StdError for EncoderError { +impl StdError for EncoderError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { - EncoderError::Body(err) => Some(err), + EncoderError::Body(err) => Some(&**err), EncoderError::Blocking(err) => Some(err), EncoderError::Io(err) => Some(err), } } } -impl From> for crate::Error { - fn from(err: EncoderError) -> Self { +impl From for crate::Error { + fn from(err: EncoderError) -> Self { crate::Error::new_encoder().with_cause(err) } } diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs index cb271c638..d51dd66c0 100644 --- a/actix-http/src/encoding/mod.rs +++ b/actix-http/src/encoding/mod.rs @@ -10,6 +10,9 @@ mod encoder; pub use self::decoder::Decoder; pub use self::encoder::Encoder; +/// Special-purpose writer for streaming (de-)compression. +/// +/// Pre-allocates 8KiB of capacity. pub(self) struct Writer { buf: BytesMut, } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 970c0c564..231e90e57 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err use derive_more::{Display, Error, From}; use http::{uri::InvalidUri, StatusCode}; -use crate::{body::AnyBody, ws, Response}; +use crate::{body::BoxBody, ws, Response}; pub use http::Error as HttpError; @@ -66,14 +66,15 @@ impl Error { } } -impl From for Response> { +impl From for Response { fn from(err: Error) -> Self { + // TODO: more appropriate error status codes, usage assessment needed let status_code = match err.inner.kind { Kind::Parse => StatusCode::BAD_REQUEST, _ => StatusCode::INTERNAL_SERVER_ERROR, }; - Response::new(status_code).set_body(AnyBody::from(err.to_string())) + Response::new(status_code).set_body(BoxBody::new(err.to_string())) } } @@ -132,12 +133,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: ws::ProtocolError) -> Self { - Self::new_ws().with_cause(err) - } -} - impl From for Error { fn from(err: HttpError) -> Self { Self::new_http().with_cause(err) @@ -150,6 +145,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: ws::ProtocolError) -> Self { + Self::new_ws().with_cause(err) + } +} + /// A set of errors that can occur during parsing HTTP streams. #[derive(Debug, Display, Error)] #[non_exhaustive] @@ -240,7 +241,7 @@ impl From for Error { } } -impl From for Response { +impl From for Response { fn from(err: ParseError) -> Self { Error::from(err).into() } @@ -337,7 +338,7 @@ pub enum DispatchError { /// Service error // FIXME: display and error type #[display(fmt = "Service Error")] - Service(#[error(not(source))] Response), + Service(#[error(not(source))] Response), /// Body error // FIXME: display and error type @@ -421,11 +422,11 @@ mod tests { #[test] fn test_into_response() { - let resp: Response = ParseError::Incomplete.into(); + let resp: Response = ParseError::Incomplete.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: Response = Error::new_http().with_cause(err).into(); + let resp: Response = Error::new_http().with_cause(err).into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -450,7 +451,7 @@ mod tests { fn test_error_http_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); let err = Error::new_io().with_cause(orig); - let resp: Response = err.into(); + let resp: Response = err.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 163d84f5b..6695d1bf3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,6 +1,5 @@ use std::{ collections::VecDeque, - error::Error as StdError, fmt, future::Future, io, mem, net, @@ -19,7 +18,7 @@ use log::{error, trace}; use pin_project::pin_project; use crate::{ - body::{AnyBody, BodySize, MessageBody}, + body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, @@ -51,13 +50,12 @@ bitflags! { pub struct Dispatcher where S: Service, - S::Error: Into>, + S::Error: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -73,13 +71,12 @@ where enum DispatcherState where S: Service, - S::Error: Into>, + S::Error: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -92,13 +89,12 @@ where struct InnerDispatcher where S: Service, - S::Error: Into>, + S::Error: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -137,13 +133,12 @@ where X: Service, B: MessageBody, - B::Error: Into>, { None, ExpectCall(#[pin] X::Future), ServiceCall(#[pin] S::Future), SendPayload(#[pin] B), - SendErrorPayload(#[pin] AnyBody), + SendErrorPayload(#[pin] BoxBody), } impl State @@ -153,7 +148,6 @@ where X: Service, B: MessageBody, - B::Error: Into>, { fn is_empty(&self) -> bool { matches!(self, State::None) @@ -171,14 +165,13 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -232,14 +225,13 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -335,7 +327,7 @@ where fn send_error_response( mut self: Pin<&mut Self>, message: Response<()>, - body: AnyBody, + body: BoxBody, ) -> Result<(), DispatchError> { let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { @@ -380,7 +372,7 @@ where // send_response would update InnerDispatcher state to SendPayload or // None(If response body is empty). // continue loop to poll it. - self.as_mut().send_error_response(res, AnyBody::empty())?; + self.as_mut().send_error_response(res, BoxBody::new(()))?; } // return with upgrade request and poll it exclusively. @@ -400,7 +392,7 @@ where // send service call error as response Poll::Ready(Err(err)) => { - let res: Response = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -497,7 +489,7 @@ where // send expect error as response Poll::Ready(Err(err)) => { - let res: Response = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.as_mut().send_error_response(res, body)?; } @@ -546,7 +538,7 @@ where // to notify the dispatcher a new state is set and the outer loop // should be continue. Poll::Ready(Err(err)) => { - let res: Response = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_error_response(res, body); } @@ -566,7 +558,7 @@ where Poll::Pending => Ok(()), // see the comment on ExpectCall state branch's Ready(Err(err)). Poll::Ready(Err(err)) => { - let res: Response = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); self.send_error_response(res, body) } @@ -772,7 +764,7 @@ where trace!("Slow request timeout"); let _ = self.as_mut().send_error_response( Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), - AnyBody::empty(), + BoxBody::new(()), ); this = self.project(); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); @@ -909,14 +901,13 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -1067,17 +1058,19 @@ mod tests { } } - fn ok_service() -> impl Service, Error = Error> + fn ok_service( + ) -> impl Service, Error = Error> { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) } fn echo_path_service( - ) -> impl Service, Error = Error> { + ) -> impl Service, Error = Error> + { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>( - Response::ok().set_body(AnyBody::copy_from_slice(path)), + Response::ok().set_body(Bytes::copy_from_slice(path)), )) }) } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 8a50417d2..70e83901c 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,5 +1,4 @@ use std::{ - error::Error as StdError, fmt, marker::PhantomData, net, @@ -16,7 +15,7 @@ use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; use crate::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, MessageBody}, config::ServiceConfig, error::DispatchError, service::HttpServiceHandler, @@ -38,7 +37,7 @@ pub struct H1Service { impl H1Service where S: ServiceFactory, - S::Error: Into>, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, @@ -63,21 +62,20 @@ impl H1Service where S: ServiceFactory, S::Future: 'static, - S::Error: Into>, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create simple tcp stream service @@ -114,16 +112,15 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into>, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -132,7 +129,7 @@ mod openssl { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create OpenSSL based service. @@ -177,16 +174,15 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into>, + S::Error: Into>, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -195,7 +191,7 @@ mod rustls { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create Rustls based service. @@ -226,7 +222,7 @@ mod rustls { impl H1Service where S: ServiceFactory, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, @@ -234,7 +230,7 @@ where pub fn expect(self, expect: X1) -> H1Service where X1: ServiceFactory, - X1::Error: Into>, + X1::Error: Into>, X1::InitError: fmt::Debug, { H1Service { @@ -277,21 +273,20 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { type Response = (); @@ -347,17 +342,16 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into>, + S::Error: Into>, S::Response: Into>, B: MessageBody, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, { type Response = (); type Error = DispatchError; diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 2547f4494..905585a32 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -1,22 +1,30 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use pin_project_lite::pin_project; -use crate::body::{BodySize, MessageBody}; -use crate::error::Error; -use crate::h1::{Codec, Message}; -use crate::response::Response; +use crate::{ + body::{BodySize, MessageBody}, + error::Error, + h1::{Codec, Message}, + response::Response, +}; -/// Send HTTP/1 response -#[pin_project::pin_project] -pub struct SendResponse { - res: Option, BodySize)>>, - #[pin] - body: Option, - #[pin] - framed: Option>, +pin_project! { + /// Send HTTP/1 response + pub struct SendResponse { + res: Option, BodySize)>>, + + #[pin] + body: Option, + + #[pin] + framed: Option>, + } } impl SendResponse diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 607997eb7..6d2f4579a 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -24,7 +24,7 @@ use log::{error, trace}; use pin_project_lite::pin_project; use crate::{ - body::{AnyBody, BodySize, MessageBody}, + body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, service::HttpFlow, OnConnectData, Payload, Request, Response, ResponseHead, @@ -51,7 +51,7 @@ where { pub(crate) fn new( flow: Rc>, - mut connection: Connection, + mut conn: Connection, on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, @@ -66,14 +66,14 @@ where }) .unwrap_or_else(|| Box::pin(sleep(dur))), on_flight: false, - ping_pong: connection.ping_pong().unwrap(), + ping_pong: conn.ping_pong().unwrap(), }); Self { flow, config, peer_addr, - connection, + connection: conn, on_connect_data, ping_pong, _phantom: PhantomData, @@ -92,12 +92,11 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into>, + S::Error: Into>, S::Future: 'static, S::Response: Into>, B: MessageBody, - B::Error: Into>, { type Output = Result<(), crate::error::DispatchError>; @@ -132,7 +131,7 @@ where let res = match fut.await { Ok(res) => handle_response(res.into(), tx, config).await, Err(err) => { - let res: Response = err.into(); + let res: Response = err.into(); handle_response(res, tx, config).await } }; @@ -207,7 +206,6 @@ async fn handle_response( ) -> Result<(), DispatchError> where B: MessageBody, - B::Error: Into>, { let (res, body) = res.replace_body(()); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 0ad17ec0a..8a9061b94 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,5 +1,4 @@ use std::{ - error::Error as StdError, future::Future, marker::PhantomData, net, @@ -19,7 +18,7 @@ use futures_core::{future::LocalBoxFuture, ready}; use log::error; use crate::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, MessageBody}, config::ServiceConfig, error::DispatchError, service::HttpFlow, @@ -39,12 +38,11 @@ pub struct H2Service { impl H2Service where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { /// Create new `H2Service` instance with config. pub(crate) fn with_config>( @@ -70,12 +68,11 @@ impl H2Service where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { /// Create plain TCP based service pub fn tcp( @@ -114,12 +111,11 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { /// Create OpenSSL based service. pub fn openssl( @@ -162,12 +158,11 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { /// Create Rustls based service. pub fn rustls( @@ -204,12 +199,11 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { type Response = (); type Error = DispatchError; @@ -244,7 +238,7 @@ where impl H2ServiceHandler where S: Service, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, @@ -267,11 +261,10 @@ impl Service<(T, Option)> for H2ServiceHandler, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into>, { type Response = (); type Error = DispatchError; @@ -320,7 +313,7 @@ pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, @@ -332,11 +325,10 @@ impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, - B::Error: Into>, { type Output = Result<(), DispatchError>; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index e0bed0631..c8e1ce6db 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -46,8 +46,8 @@ pub trait Head: Default + 'static { #[derive(Debug)] pub struct RequestHead { - pub uri: Uri, pub method: Method, + pub uri: Uri, pub version: Version, pub headers: HeaderMap, pub extensions: RefCell, @@ -58,13 +58,13 @@ pub struct RequestHead { impl Default for RequestHead { fn default() -> RequestHead { RequestHead { - uri: Uri::default(), method: Method::default(), + uri: Uri::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - flags: Flags::empty(), - peer_addr: None, extensions: RefCell::new(Extensions::new()), + peer_addr: None, + flags: Flags::empty(), } } } @@ -192,6 +192,7 @@ impl RequestHead { } #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub enum RequestHeadType { Owned(RequestHead), Rc(Rc, Option), diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 47f1c37e2..ad41094ae 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -6,14 +6,15 @@ use std::{ }; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use crate::{ - body::{AnyBody, MessageBody}, - error::Error, + body::{BoxBody, MessageBody}, extensions::Extensions, + header::{self, IntoHeaderValue}, http::{HeaderMap, StatusCode}, message::{BoxedResponseHead, ResponseHead}, - ResponseBuilder, + Error, ResponseBuilder, }; /// An HTTP response. @@ -22,13 +23,13 @@ pub struct Response { pub(crate) body: B, } -impl Response { +impl Response { /// Constructs a new response with default body. #[inline] pub fn new(status: StatusCode) -> Self { Response { head: BoxedResponseHead::new(status), - body: AnyBody::empty(), + body: BoxBody::new(()), } } @@ -189,6 +190,14 @@ impl Response { } } + #[inline] + pub fn map_into_boxed_body(self) -> Response + where + B: MessageBody + 'static, + { + self.map_body(|_, body| BoxBody::new(body)) + } + /// Returns body, consuming this response. pub fn into_body(self) -> B { self.body @@ -223,81 +232,99 @@ impl Default for Response { } } -impl>, E: Into> From> - for Response +impl>, E: Into> From> + for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), - Err(err) => err.into().into(), + Err(err) => Response::from(err.into()), } } } -impl From for Response { +impl From for Response { fn from(mut builder: ResponseBuilder) -> Self { - builder.finish() + builder.finish().map_into_boxed_body() } } -impl From for Response { +impl From for Response { fn from(val: std::convert::Infallible) -> Self { match val {} } } -impl From<&'static str> for Response { +impl From<&'static str> for Response<&'static str> { fn from(val: &'static str) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(val) + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } -impl From<&'static [u8]> for Response { +impl From<&'static [u8]> for Response<&'static [u8]> { fn from(val: &'static [u8]) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(val) + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(val) + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } -impl<'a> From<&'a String> for Response { - fn from(val: &'a String) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::TEXT_PLAIN_UTF_8) - .body(val) +impl From<&String> for Response { + fn from(val: &String) -> Self { + let mut res = Response::with_body(StatusCode::OK, val.clone()); + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(val) + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { - Response::build(StatusCode::OK) - .content_type(mime::APPLICATION_OCTET_STREAM) - .body(val) + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res + } +} + +impl From for Response { + fn from(val: ByteString) -> Self { + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res } } #[cfg(test)] mod tests { use super::*; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::{ + body::to_bytes, + http::header::{HeaderValue, CONTENT_TYPE, COOKIE}, + }; #[test] fn test_debug() { @@ -309,73 +336,73 @@ mod tests { assert!(dbg.contains("Response")); } - #[test] - fn test_into_response() { - let resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); + #[actix_rt::test] + async fn test_into_response() { + let res = Response::from("test"); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); - let resp: Response = b"test".as_ref().into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from(b"test".as_ref()); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); - let resp: Response = "test".to_owned().into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from("test".to_owned()); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); - let resp: Response = (&"test".to_owned()).into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from("test".to_owned()); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from(b); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from(b); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); let b = BytesMut::from("test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); + let res = Response::from(b); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]); } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index c5fcb625c..0537112d5 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -2,19 +2,11 @@ use std::{ cell::{Ref, RefMut}, - error::Error as StdError, - fmt, - future::Future, - pin::Pin, - str, - task::{Context, Poll}, + fmt, str, }; -use bytes::Bytes; -use futures_core::Stream; - use crate::{ - body::{AnyBody, BodyStream}, + body::{EitherBody, MessageBody}, error::{Error, HttpError}, header::{self, IntoHeaderPair, IntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, @@ -235,10 +227,14 @@ impl ResponseBuilder { /// Generate response with a wrapped body. /// /// This `ResponseBuilder` will be left in a useless state. - #[inline] - pub fn body>(&mut self, body: B) -> Response { - self.message_body(body.into()) - .unwrap_or_else(Response::from) + pub fn body(&mut self, body: B) -> Response> + where + B: MessageBody + 'static, + { + match self.message_body(body) { + Ok(res) => res.map_body(|_, body| EitherBody::left(body)), + Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)), + } } /// Generate response with a body. @@ -253,24 +249,12 @@ impl ResponseBuilder { Ok(Response { head, body }) } - /// Generate response with a streaming body. - /// - /// This `ResponseBuilder` will be left in a useless state. - #[inline] - pub fn streaming(&mut self, stream: S) -> Response - where - S: Stream> + 'static, - E: Into> + 'static, - { - self.body(AnyBody::new_boxed(BodyStream::new(stream))) - } - /// Generate response with an empty body. /// /// This `ResponseBuilder` will be left in a useless state. #[inline] - pub fn finish(&mut self) -> Response { - self.body(AnyBody::empty()) + pub fn finish(&mut self) -> Response> { + self.body(()) } /// Create an owned `ResponseBuilder`, leaving the original in a useless state. @@ -327,14 +311,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } -impl Future for ResponseBuilder { - type Output = Result, Error>; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(self.finish())) - } -} - impl fmt::Debug for ResponseBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let head = self.head.as_ref().unwrap(); @@ -356,8 +332,9 @@ impl fmt::Debug for ResponseBuilder { #[cfg(test)] mod tests { + use bytes::Bytes; + use super::*; - use crate::body::AnyBody; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] @@ -383,20 +360,28 @@ mod tests { #[test] fn test_force_close() { let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive()) + assert!(!resp.keep_alive()); } #[test] fn test_content_type() { let resp = Response::build(StatusCode::OK) .content_type("text/plain") - .body(AnyBody::empty()); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + .body(Bytes::new()); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain"); + + let resp = Response::build(StatusCode::OK) + .content_type(mime::APPLICATION_JAVASCRIPT_UTF_8) + .body(Bytes::new()); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "application/javascript; charset=utf-8" + ); } #[test] fn test_into_builder() { - let mut resp: Response = "test".into(); + let mut resp: Response<_> = "test".into(); assert_eq!(resp.status(), StatusCode::OK); resp.headers_mut().insert( diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index fb0cccb38..7af34ba05 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,5 +1,4 @@ use std::{ - error::Error as StdError, fmt, future::Future, marker::PhantomData, @@ -15,10 +14,10 @@ use actix_service::{ fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, }; use futures_core::{future::LocalBoxFuture, ready}; -use pin_project::pin_project; +use pin_project_lite::pin_project; use crate::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, config::{KeepAlive, ServiceConfig}, error::DispatchError, @@ -38,7 +37,7 @@ pub struct HttpService { impl HttpService where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, @@ -53,12 +52,11 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -93,7 +91,7 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, @@ -107,7 +105,7 @@ where pub fn expect(self, expect: X1) -> HttpService where X1: ServiceFactory, - X1::Error: Into>, + X1::Error: Into>, X1::InitError: fmt::Debug, { HttpService { @@ -151,17 +149,16 @@ impl HttpService where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -170,7 +167,7 @@ where Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create simple tcp stream service @@ -208,17 +205,16 @@ mod openssl { where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -227,7 +223,7 @@ mod openssl { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create OpenSSL based service. @@ -281,17 +277,16 @@ mod rustls { where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory< @@ -300,7 +295,7 @@ mod rustls { Response = (), >, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { /// Create Rustls based service. @@ -348,22 +343,21 @@ where S: ServiceFactory, S::Future: 'static, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, X: ServiceFactory, X::Future: 'static, - X::Error: Into>, + X::Error: Into>, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { type Response = (); @@ -426,11 +420,11 @@ where impl HttpServiceHandler where S: Service, - S::Error: Into>, + S::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed)>, - U::Error: Into>, + U::Error: Into>, { pub(super) fn new( cfg: ServiceConfig, @@ -450,7 +444,7 @@ where pub(super) fn _poll_ready( &self, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?; ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?; @@ -486,18 +480,17 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display + Into>, + U::Error: fmt::Display + Into>, { type Response = (); type Error = DispatchError; @@ -519,23 +512,27 @@ where match proto { Protocol::Http2 => HttpServiceHandlerResponse { - state: State::H2Handshake(Some(( - h2::handshake_with_timeout(io, &self.cfg), - self.cfg.clone(), - self.flow.clone(), - on_connect_data, - peer_addr, - ))), + state: State::H2Handshake { + handshake: Some(( + h2::handshake_with_timeout(io, &self.cfg), + self.cfg.clone(), + self.flow.clone(), + on_connect_data, + peer_addr, + )), + }, }, Protocol::Http1 => HttpServiceHandlerResponse { - state: State::H1(h1::Dispatcher::new( - io, - self.cfg.clone(), - self.flow.clone(), - on_connect_data, - peer_addr, - )), + state: State::H1 { + dispatcher: h1::Dispatcher::new( + io, + self.cfg.clone(), + self.flow.clone(), + on_connect_data, + peer_addr, + ), + }, }, proto => unimplemented!("Unsupported HTTP version: {:?}.", proto), @@ -543,58 +540,65 @@ where } } -#[pin_project(project = StateProj)] -enum State -where - T: AsyncRead + AsyncWrite + Unpin, +pin_project! { + #[project = StateProj] + enum State + where + T: AsyncRead, + T: AsyncWrite, + T: Unpin, - S: Service, - S::Future: 'static, - S::Error: Into>, + S: Service, + S::Future: 'static, + S::Error: Into>, - B: MessageBody, - B::Error: Into>, + B: MessageBody, - X: Service, - X::Error: Into>, + X: Service, + X::Error: Into>, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - H1(#[pin] h1::Dispatcher), - H2(#[pin] h2::Dispatcher), - H2Handshake( - Option<( - h2::HandshakeWithTimeout, - ServiceConfig, - Rc>, - OnConnectData, - Option, - )>, - ), + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + H1 { #[pin] dispatcher: h1::Dispatcher }, + H2 { #[pin] dispatcher: h2::Dispatcher }, + H2Handshake { + handshake: Option<( + h2::HandshakeWithTimeout, + ServiceConfig, + Rc>, + OnConnectData, + Option, + )>, + }, + } } -#[pin_project] -pub struct HttpServiceHandlerResponse -where - T: AsyncRead + AsyncWrite + Unpin, +pin_project! { + pub struct HttpServiceHandlerResponse + where + T: AsyncRead, + T: AsyncWrite, + T: Unpin, - S: Service, - S::Error: Into> + 'static, - S::Future: 'static, - S::Response: Into> + 'static, + S: Service, + S::Error: Into>, + S::Error: 'static, + S::Future: 'static, + S::Response: Into>, + S::Response: 'static, - B: MessageBody, - B::Error: Into>, + B: MessageBody, - X: Service, - X::Error: Into>, + X: Service, + X::Error: Into>, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - #[pin] - state: State, + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + #[pin] + state: State, + } } impl Future for HttpServiceHandlerResponse @@ -602,15 +606,14 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, - S::Error: Into> + 'static, + S::Error: Into> + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - B::Error: Into>, X: Service, - X::Error: Into>, + X::Error: Into>, U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, @@ -619,23 +622,24 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project().state.project() { - StateProj::H1(disp) => disp.poll(cx), - StateProj::H2(disp) => disp.poll(cx), - StateProj::H2Handshake(data) => { + StateProj::H1 { dispatcher } => dispatcher.poll(cx), + StateProj::H2 { dispatcher } => dispatcher.poll(cx), + StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { - let (_, cfg, srv, on_connect_data, peer_addr) = + let (_, config, flow, on_connect_data, peer_addr) = data.take().unwrap(); - self.as_mut().project().state.set(State::H2( - h2::Dispatcher::new( - srv, + + self.as_mut().project().state.set(State::H2 { + dispatcher: h2::Dispatcher::new( + flow, conn, on_connect_data, - cfg, + config, peer_addr, timer, ), - )); + }); self.poll(cx) } Err(err) => { diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index f49cbe5d4..a3f766e9c 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -4,17 +4,21 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; +use pin_project_lite::pin_project; use super::{Codec, Frame, Message}; -#[pin_project::pin_project] -pub struct Dispatcher -where - S: Service + 'static, - T: AsyncRead + AsyncWrite, -{ - #[pin] - inner: inner::Dispatcher, +pin_project! { + pub struct Dispatcher + where + S: Service, + S: 'static, + T: AsyncRead, + T: AsyncWrite, + { + #[pin] + inner: inner::Dispatcher, + } } impl Dispatcher @@ -72,7 +76,7 @@ mod inner { use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; - use crate::{body::AnyBody, Response}; + use crate::{body::BoxBody, Response}; /// Framed transport errors pub enum DispatcherError @@ -136,7 +140,7 @@ mod inner { } } - impl From> for Response + impl From> for Response where E: fmt::Debug + fmt::Display, U: Encoder + Decoder, @@ -144,7 +148,7 @@ mod inner { ::Error: fmt::Debug, { fn from(err: DispatcherError) -> Self { - Response::internal_server_error().set_body(AnyBody::from(err.to_string())) + Response::internal_server_error().set_body(BoxBody::new(err.to_string())) } } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 70e0e62a2..cb1aa6730 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -8,9 +8,9 @@ use std::io; use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; +use crate::body::BoxBody; use crate::{ - body::AnyBody, header::HeaderValue, message::RequestHead, response::Response, - ResponseBuilder, + header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder, }; mod codec; @@ -69,7 +69,7 @@ pub enum ProtocolError { } /// WebSocket handshake errors -#[derive(Debug, PartialEq, Display, Error)] +#[derive(Debug, Clone, Copy, PartialEq, Display, Error)] pub enum HandshakeError { /// Only get method is allowed. #[display(fmt = "Method not allowed.")] @@ -96,8 +96,8 @@ pub enum HandshakeError { BadWebsocketKey, } -impl From<&HandshakeError> for Response { - fn from(err: &HandshakeError) -> Self { +impl From for Response { + fn from(err: HandshakeError) -> Self { match err { HandshakeError::GetMethodRequired => { let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED); @@ -139,9 +139,9 @@ impl From<&HandshakeError> for Response { } } -impl From for Response { - fn from(err: HandshakeError) -> Self { - (&err).into() +impl From<&HandshakeError> for Response { + fn from(err: &HandshakeError) -> Self { + (*err).into() } } @@ -220,9 +220,10 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { #[cfg(test)] mod tests { + use crate::{header, Method}; + use super::*; - use crate::{body::AnyBody, test::TestRequest}; - use http::{header, Method}; + use crate::test::TestRequest; #[test] fn test_handshake() { @@ -336,17 +337,17 @@ mod tests { #[test] fn test_ws_error_http_response() { - let resp: Response = HandshakeError::GetMethodRequired.into(); + let resp: Response = HandshakeError::GetMethodRequired.into(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: Response = HandshakeError::NoWebsocketUpgrade.into(); + let resp: Response = HandshakeError::NoWebsocketUpgrade.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoConnectionUpgrade.into(); + let resp: Response = HandshakeError::NoConnectionUpgrade.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoVersionHeader.into(); + let resp: Response = HandshakeError::NoVersionHeader.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::UnsupportedVersion.into(); + let resp: Response = HandshakeError::UnsupportedVersion.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::BadWebsocketKey.into(); + let resp: Response = HandshakeError::BadWebsocketKey.into(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 414266d81..4c923873f 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use actix_http::{ - body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response, + body::BoxBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response, }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; @@ -99,7 +99,7 @@ async fn test_with_query_parameter() { #[display(fmt = "expect failed")] struct ExpectFailed; -impl From for Response { +impl From for Response { fn from(_: ExpectFailed) -> Self { Response::new(StatusCode::EXPECTATION_FAILED) } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index e7dd78171..86ee17c74 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -5,7 +5,7 @@ extern crate tls_openssl as openssl; use std::{convert::Infallible, io}; use actix_http::{ - body::{AnyBody, SizedStream}, + body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, http::{ header::{self, HeaderValue}, @@ -348,7 +348,7 @@ async fn test_h2_body_chunked_explicit() { ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) - .streaming(body), + .body(BodyStream::new(body)), ) }) .openssl(tls_config()) @@ -399,9 +399,11 @@ async fn test_h2_response_http_error_handling() { #[display(fmt = "error")] struct BadRequest; -impl From for Response { +impl From for Response { fn from(err: BadRequest) -> Self { - Response::build(StatusCode::BAD_REQUEST).body(err.to_string()) + Response::build(StatusCode::BAD_REQUEST) + .body(err.to_string()) + .map_into_boxed_body() } } @@ -409,7 +411,7 @@ impl From for Response { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, _>(BadRequest)) + .h2(|_| err::, _>(BadRequest)) .openssl(tls_config()) .map_err(|_| ()) }) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index b5289bf7c..873752779 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -10,7 +10,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, SizedStream}, + body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, http::{ header::{self, HeaderName, HeaderValue}, @@ -416,7 +416,7 @@ async fn test_h2_body_chunked_explicit() { ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) - .streaming(body), + .body(BodyStream::new(body)), ) }) .rustls(tls_config()) @@ -467,9 +467,9 @@ async fn test_h2_response_http_error_handling() { #[display(fmt = "error")] struct BadRequest; -impl From for Response { +impl From for Response { fn from(_: BadRequest) -> Self { - Response::bad_request().set_body(AnyBody::from("error")) + Response::bad_request().set_body(BoxBody::new("error")) } } @@ -477,7 +477,7 @@ impl From for Response { async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| err::, _>(BadRequest)) + .h2(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; @@ -494,7 +494,7 @@ async fn test_h2_service_error() { async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() - .h1(|_| err::, _>(BadRequest)) + .h1(|_| err::, _>(BadRequest)) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 11bc8e939..adf2a28ca 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -6,7 +6,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, SizedStream}, + body::{self, BodyStream, BoxBody, SizedStream}, header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode, }; @@ -69,7 +69,7 @@ async fn test_h1_2() { #[display(fmt = "expect failed")] struct ExpectFailed; -impl From for Response { +impl From for Response { fn from(_: ExpectFailed) -> Self { Response::new(StatusCode::EXPECTATION_FAILED) } @@ -622,7 +622,7 @@ async fn test_h1_body_chunked_explicit() { ok::<_, Infallible>( Response::build(StatusCode::OK) .insert_header((header::TRANSFER_ENCODING, "chunked")) - .streaming(body), + .body(BodyStream::new(body)), ) }) .tcp() @@ -656,7 +656,9 @@ async fn test_h1_body_chunked_implicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body)) + ok::<_, Infallible>( + Response::build(StatusCode::OK).body(BodyStream::new(body)), + ) }) .tcp() }) @@ -714,9 +716,9 @@ async fn test_h1_response_http_error_handling() { #[display(fmt = "error")] struct BadRequest; -impl From for Response { +impl From for Response { fn from(_: BadRequest) -> Self { - Response::bad_request().set_body(AnyBody::from("error")) + Response::bad_request().set_body(BoxBody::new("error")) } } @@ -724,7 +726,7 @@ impl From for Response { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| err::, _>(BadRequest)) + .h1(|_| err::, _>(BadRequest)) .tcp() }) .await; @@ -773,36 +775,35 @@ async fn test_not_modified_spec_h1() { let mut srv = test_server(|| { HttpService::build() .h1(|req: Request| { - let res: Response = match req.path() { + let res: Response = match req.path() { // with no content-length "/none" => { - Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None) + Response::with_body(StatusCode::NOT_MODIFIED, body::None::new()) + .map_into_boxed_body() } // with no content-length - "/body" => Response::with_body( - StatusCode::NOT_MODIFIED, - AnyBody::from("1234"), - ), + "/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234") + .map_into_boxed_body(), // with manual content-length header and specific None body "/cl-none" => { - let mut res = - Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None); + let mut res = Response::with_body( + StatusCode::NOT_MODIFIED, + body::None::new(), + ); res.headers_mut() .insert(CL.clone(), header::HeaderValue::from_static("24")); - res + res.map_into_boxed_body() } // with manual content-length header and ignore-able body "/cl-body" => { - let mut res = Response::with_body( - StatusCode::NOT_MODIFIED, - AnyBody::from("1234"), - ); + let mut res = + Response::with_body(StatusCode::NOT_MODIFIED, "1234"); res.headers_mut() .insert(CL.clone(), header::HeaderValue::from_static("4")); - res + res.map_into_boxed_body() } _ => panic!("unknown route"), diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 6d0de2316..c91382013 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -6,7 +6,7 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{ - body::{AnyBody, BodySize}, + body::{BodySize, BoxBody}, h1, ws::{self, CloseCode, Frame, Item, Message}, Error, HttpService, Request, Response, @@ -50,14 +50,14 @@ enum WsServiceError { Dispatcher, } -impl From for Response { +impl From for Response { fn from(err: WsServiceError) -> Self { match err { WsServiceError::Http(err) => err.into(), WsServiceError::Ws(err) => err.into(), WsServiceError::Io(_err) => unreachable!(), WsServiceError::Dispatcher => Response::internal_server_error() - .set_body(AnyBody::from(format!("{}", err))), + .set_body(BoxBody::new(format!("{}", err))), } } } diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index b80918ec0..1decd6e98 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -31,7 +31,7 @@ extern crate tls_openssl as openssl; #[cfg(feature = "rustls")] extern crate tls_rustls as rustls; -use std::{error::Error as StdError, fmt, net, thread, time::Duration}; +use std::{fmt, net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; @@ -41,7 +41,8 @@ use actix_http::{ }; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_web::{ - dev::{AppConfig, MessageBody, Server, ServerHandle, Service}, + body::MessageBody, + dev::{AppConfig, Server, ServerHandle, Service}, rt::{self, System}, web, Error, }; @@ -88,7 +89,6 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { start_with(TestServerConfig::default(), factory) } @@ -128,7 +128,6 @@ where S::Response: Into> + 'static, >::Future: 'static, B: MessageBody + 'static, - B::Error: Into>, { // for sending handles and server info back from the spawned thread let (started_tx, started_rx) = std::sync::mpsc::channel(); diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e7b8cd0f0..28b5b29ea 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -22,7 +22,7 @@ actix-web = { version = "4.0.0-beta.11", default-features = false } bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } -pin-project = "1.0.0" +pin-project-lite = "0.2" tokio = { version = "1", features = ["sync"] } [dev-dependencies] diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index f0a53d4e0..b6323c2ed 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -30,6 +30,7 @@ use actix_web::{ use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::Stream; +use pin_project_lite::pin_project; use tokio::sync::oneshot::Sender; /// Perform WebSocket handshake and start actor. @@ -462,13 +463,14 @@ where } } -#[pin_project::pin_project] -struct WsStream { - #[pin] - stream: S, - decoder: Codec, - buf: BytesMut, - closed: bool, +pin_project! { + struct WsStream { + #[pin] + stream: S, + decoder: Codec, + buf: BytesMut, + closed: bool, + } } impl WsStream diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 0a8e50b3e..e481b2839 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -78,7 +78,7 @@ async fn test_with_credentials() { match srv.ws().await { Ok(_) => panic!("WebSocket client without credentials should panic"), Err(awc::error::WsClientError::InvalidResponseStatus(status)) => { - assert_eq!(status, StatusCode::UNAUTHORIZED) + assert_eq!(status, StatusCode::UNAUTHORIZED); } Err(e) => panic!("Invalid error from WebSocket client: {}", e), } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 851e5cd43..fc60f5edb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -105,6 +105,7 @@ brotli2 = "0.3.2" env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } +static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs new file mode 100644 index 000000000..cb9038ff3 --- /dev/null +++ b/awc/src/any_body.rs @@ -0,0 +1,266 @@ +use std::{ + borrow::Cow, + fmt, mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Bytes, BytesMut}; +use futures_core::Stream; +use pin_project_lite::pin_project; + +use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream}; + +use crate::BoxError; + +pin_project! { + /// Represents various types of HTTP message body. + #[derive(Clone)] + #[project = AnyBodyProj] + pub enum AnyBody { + /// Empty response. `Content-Length` header is not set. + None, + + /// Complete, in-memory response body. + Bytes { body: Bytes }, + + /// Generic / Other message body. + Body { #[pin] body: B }, + } +} + +impl AnyBody { + /// Constructs a "body" representing an empty response. + pub fn none() -> Self { + Self::None + } + + /// Constructs a new, 0-length body. + pub fn empty() -> Self { + Self::Bytes { body: Bytes::new() } + } + + /// Create boxed body from generic message body. + pub fn new_boxed(body: B) -> Self + where + B: MessageBody + 'static, + { + Self::Body { + body: BoxBody::new(body), + } + } + + /// Constructs new `AnyBody` instance from a slice of bytes by copying it. + /// + /// If your bytes container is owned, it may be cheaper to use a `From` impl. + pub fn copy_from_slice(s: &[u8]) -> Self { + Self::Bytes { + body: Bytes::copy_from_slice(s), + } + } + + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")] + pub fn from_slice(s: &[u8]) -> Self { + Self::Bytes { + body: Bytes::copy_from_slice(s), + } + } +} + +impl AnyBody { + /// Create body from generic message body. + pub fn new(body: B) -> Self { + Self::Body { body } + } +} + +impl AnyBody +where + B: MessageBody + 'static, +{ + pub fn into_boxed(self) -> AnyBody { + match self { + Self::None => AnyBody::None, + Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes }, + Self::Body { body } => AnyBody::new_boxed(body), + } + } +} + +impl MessageBody for AnyBody +where + B: MessageBody, +{ + type Error = crate::BoxError; + + fn size(&self) -> BodySize { + match self { + AnyBody::None => BodySize::None, + AnyBody::Bytes { ref body } => BodySize::Sized(body.len() as u64), + AnyBody::Body { ref body } => body.size(), + } + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.project() { + AnyBodyProj::None => Poll::Ready(None), + AnyBodyProj::Bytes { body } => { + let len = body.len(); + if len == 0 { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(mem::take(body)))) + } + } + + AnyBodyProj::Body { body } => body.poll_next(cx).map_err(|err| err.into()), + } + } +} + +impl PartialEq for AnyBody { + fn eq(&self, other: &AnyBody) -> bool { + match self { + AnyBody::None => matches!(*other, AnyBody::None), + AnyBody::Bytes { body } => match other { + AnyBody::Bytes { body: b2 } => body == b2, + _ => false, + }, + AnyBody::Body { .. } => false, + } + } +} + +impl fmt::Debug for AnyBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AnyBody::None => write!(f, "AnyBody::None"), + AnyBody::Bytes { ref body } => write!(f, "AnyBody::Bytes({:?})", body), + AnyBody::Body { ref body } => write!(f, "AnyBody::Message({:?})", body), + } + } +} + +impl From<&'static str> for AnyBody { + fn from(string: &'static str) -> Self { + Self::Bytes { + body: Bytes::from_static(string.as_ref()), + } + } +} + +impl From<&'static [u8]> for AnyBody { + fn from(bytes: &'static [u8]) -> Self { + Self::Bytes { + body: Bytes::from_static(bytes), + } + } +} + +impl From> for AnyBody { + fn from(vec: Vec) -> Self { + Self::Bytes { + body: Bytes::from(vec), + } + } +} + +impl From for AnyBody { + fn from(string: String) -> Self { + Self::Bytes { + body: Bytes::from(string), + } + } +} + +impl From<&'_ String> for AnyBody { + fn from(string: &String) -> Self { + Self::Bytes { + body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)), + } + } +} + +impl From> for AnyBody { + fn from(string: Cow<'_, str>) -> Self { + match string { + Cow::Owned(s) => Self::from(s), + Cow::Borrowed(s) => Self::Bytes { + body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)), + }, + } + } +} + +impl From for AnyBody { + fn from(bytes: Bytes) -> Self { + Self::Bytes { body: bytes } + } +} + +impl From for AnyBody { + fn from(bytes: BytesMut) -> Self { + Self::Bytes { + body: bytes.freeze(), + } + } +} + +impl From> for AnyBody +where + S: Stream> + 'static, + E: Into + 'static, +{ + fn from(stream: SizedStream) -> Self { + AnyBody::new_boxed(stream) + } +} + +impl From> for AnyBody +where + S: Stream> + 'static, + E: Into + 'static, +{ + fn from(stream: BodyStream) -> Self { + AnyBody::new_boxed(stream) + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomPinned; + + use static_assertions::{assert_impl_all, assert_not_impl_all}; + + use super::*; + + struct PinType(PhantomPinned); + + impl MessageBody for PinType { + type Error = crate::BoxError; + + fn size(&self) -> BodySize { + unimplemented!() + } + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll>> { + unimplemented!() + } + } + + assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody>: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Send, Sync, Unpin); + assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin); + assert_impl_all!(AnyBody: MessageBody); + + assert_not_impl_all!(AnyBody: Send, Sync, Unpin); + assert_not_impl_all!(AnyBody: Send, Sync, Unpin); +} diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs index 6bbc9ad07..0e1f0bfec 100644 --- a/awc/src/client/connection.rs +++ b/awc/src/client/connection.rs @@ -12,9 +12,9 @@ use bytes::Bytes; use futures_core::future::LocalBoxFuture; use h2::client::SendRequest; -use actix_http::{ - body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead, -}; +use actix_http::{body::MessageBody, h1::ClientCodec, Payload, RequestHeadType, ResponseHead}; + +use crate::BoxError; use super::error::SendRequestError; use super::pool::Acquired; @@ -254,7 +254,7 @@ where where H: Into + 'static, RB: MessageBody + 'static, - RB::Error: Into, + RB::Error: Into, { Box::pin(async move { match self { diff --git a/awc/src/client/error.rs b/awc/src/client/error.rs index 68ffb6fbf..d351b5d5e 100644 --- a/awc/src/client/error.rs +++ b/awc/src/client/error.rs @@ -1,13 +1,13 @@ -use std::{error::Error as StdError, fmt, io}; +use std::{fmt, io}; use derive_more::{Display, From}; -use actix_http::{ - error::{Error, ParseError}, - http::Error as HttpError, -}; +use actix_http::{error::ParseError, http::Error as HttpError}; + #[cfg(feature = "openssl")] -use actix_tls::accept::openssl::reexports::Error as OpenSslError; +use actix_tls::accept::openssl::reexports::Error as OpensslError; + +use crate::BoxError; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] @@ -20,7 +20,7 @@ pub enum ConnectError { /// SSL error #[cfg(feature = "openssl")] #[display(fmt = "{}", _0)] - SslError(OpenSslError), + SslError(OpensslError), /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] @@ -118,11 +118,11 @@ pub enum SendRequestError { TunnelNotSupported, /// Error sending request body - Body(Error), + Body(BoxError), /// Other errors that can occur after submitting a request. #[display(fmt = "{:?}: {}", _1, _0)] - Custom(Box, Box), + Custom(BoxError, Box), } impl std::error::Error for SendRequestError {} @@ -141,7 +141,7 @@ pub enum FreezeRequestError { /// Other errors that can occur after submitting a request. #[display(fmt = "{:?}: {}", _1, _0)] - Custom(Box, Box), + Custom(BoxError, Box), } impl std::error::Error for FreezeRequestError {} diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index c442cd4cf..b26a97eeb 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -13,7 +13,7 @@ use actix_http::{ header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, StatusCode, }, - Error, Payload, RequestHeadType, ResponseHead, + Payload, RequestHeadType, ResponseHead, }; use actix_utils::future::poll_fn; use bytes::buf::BufMut; @@ -22,6 +22,8 @@ use futures_core::{ready, Stream}; use futures_util::SinkExt as _; use pin_project_lite::pin_project; +use crate::BoxError; + use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; @@ -33,7 +35,7 @@ pub(crate) async fn send_request( where Io: ConnectionIo, B: MessageBody, - B::Error: Into, + B::Error: Into, { // set request host header if !head.as_ref().headers.contains_key(HOST) @@ -155,7 +157,7 @@ pub(crate) async fn send_body( where Io: ConnectionIo, B: MessageBody, - B::Error: Into, + B::Error: Into, { actix_rt::pin!(body); @@ -166,7 +168,7 @@ where Some(Ok(chunk)) => { framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?; } - Some(Err(err)) => return Err(err.into().into()), + Some(Err(err)) => return Err(SendRequestError::Body(err.into())), None => { eof = true; framed.as_mut().write(h1::Message::Chunk(None))?; diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs index 66fb790a3..9ced5776b 100644 --- a/awc/src/client/h2proto.rs +++ b/awc/src/client/h2proto.rs @@ -13,9 +13,11 @@ use log::trace; use actix_http::{ body::{BodySize, MessageBody}, header::HeaderMap, - Error, Payload, RequestHeadType, ResponseHead, + Payload, RequestHeadType, ResponseHead, }; +use crate::BoxError; + use super::{ config::ConnectorConfig, connection::{ConnectionIo, H2Connection}, @@ -30,7 +32,7 @@ pub(crate) async fn send_request( where Io: ConnectionIo, B: MessageBody, - B::Error: Into, + B::Error: Into, { trace!("Sending client request: {:?} {:?}", head, body.size()); @@ -133,10 +135,12 @@ where async fn send_body(body: B, mut send: SendStream) -> Result<(), SendRequestError> where B: MessageBody, - B::Error: Into, + B::Error: Into, { let mut buf = None; + actix_rt::pin!(body); + loop { if buf.is_none() { match poll_fn(|cx| body.as_mut().poll_next(cx)).await { @@ -144,10 +148,10 @@ where send.reserve_capacity(b.len()); buf = Some(b); } - Some(Err(e)) => return Err(e.into().into()), + Some(Err(err)) => return Err(SendRequestError::Body(err.into())), None => { - if let Err(e) = send.send_data(Bytes::new(), true) { - return Err(e.into()); + if let Err(err) = send.send_data(Bytes::new(), true) { + return Err(err.into()); } send.reserve_capacity(0); return Ok(()); diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 05f2a6495..19870b069 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -7,16 +7,17 @@ use std::{ }; use actix_codec::Framed; -use actix_http::{ - body::AnyBody, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead, -}; +use actix_http::{h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead}; use actix_service::Service; use futures_core::{future::LocalBoxFuture, ready}; -use crate::client::{ - Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, +use crate::{ + any_body::AnyBody, + client::{ + Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, + }, + response::ClientResponse, }; -use crate::response::ClientResponse; pub type BoxConnectorService = Rc< dyn Service< diff --git a/awc/src/error.rs b/awc/src/error.rs index d415efe95..726e1a506 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -11,6 +11,8 @@ use serde_json::error::Error as JsonError; pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; +// TODO: address display, error, and from impls + /// Websocket client error #[derive(Debug, Display, From)] pub enum WsClientError { diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 46a00b000..472397359 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -1,18 +1,18 @@ -use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration}; +use std::{convert::TryFrom, net, rc::Rc, time::Duration}; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; use actix_http::{ - body::AnyBody, http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, RequestHead, }; use crate::{ + any_body::AnyBody, sender::{RequestSender, SendClientRequest}, - ClientConfig, + BoxError, ClientConfig, }; /// `FrozenClientRequest` struct represents cloneable client request. @@ -82,7 +82,7 @@ impl FrozenClientRequest { pub fn send_stream(&self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into> + 'static, + E: Into + 'static, { RequestSender::Rc(self.head.clone(), None).send_stream( self.addr, @@ -207,7 +207,7 @@ impl FrozenSendBuilder { pub fn send_stream(self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into> + 'static, + E: Into + 'static, { if let Some(e) = self.err { return e.into(); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index fc99419eb..2f4183120 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -104,6 +104,7 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +mod any_body; mod builder; mod client; mod connect; @@ -139,6 +140,8 @@ use actix_service::Service; use self::client::{ConnectInfo, TcpConnectError, TcpConnection}; +pub(crate) type BoxError = Box; + /// An asynchronous HTTP and WebSocket client. /// /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 12a71f7cb..89cff22cd 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -8,7 +8,6 @@ use std::{ }; use actix_http::{ - body::AnyBody, http::{header, Method, StatusCode, Uri}, RequestHead, RequestHeadType, }; @@ -17,10 +16,12 @@ use bytes::Bytes; use futures_core::ready; use super::Transform; - -use crate::client::{InvalidUrl, SendRequestError}; -use crate::connect::{ConnectRequest, ConnectResponse}; -use crate::ClientResponse; +use crate::{ + any_body::AnyBody, + client::{InvalidUrl, SendRequestError}, + connect::{ConnectRequest, ConnectResponse}, + ClientResponse, +}; pub struct Redirect { max_redirect_times: u8, @@ -95,7 +96,7 @@ where }; let body_opt = match body { - AnyBody::Bytes(ref b) => Some(b.clone()), + AnyBody::Bytes { ref body } => Some(body.clone()), _ => None, }; @@ -192,7 +193,9 @@ where let body_new = if is_redirect { // try to reuse body match body { - Some(ref bytes) => AnyBody::Bytes(bytes.clone()), + Some(ref bytes) => AnyBody::Bytes { + body: bytes.clone(), + }, // TODO: should this be AnyBody::Empty or AnyBody::None. _ => AnyBody::empty(), } diff --git a/awc/src/request.rs b/awc/src/request.rs index f364b43c7..d26b703f6 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,11 +1,10 @@ -use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration}; +use std::{convert::TryFrom, fmt, net, rc::Rc, time::Duration}; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; use actix_http::{ - body::AnyBody, http::{ header::{self, IntoHeaderPair}, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, @@ -13,15 +12,17 @@ use actix_http::{ RequestHead, }; -#[cfg(feature = "cookies")] -use crate::cookie::{Cookie, CookieJar}; use crate::{ + any_body::AnyBody, error::{FreezeRequestError, InvalidUrl}, frozen::FrozenClientRequest, sender::{PrepForSendingError, RequestSender, SendClientRequest}, - ClientConfig, + BoxError, ClientConfig, }; +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; + /// An HTTP Client request builder /// /// This type can be used to construct an instance of `ClientRequest` through a @@ -404,7 +405,7 @@ impl ClientRequest { pub fn send_stream(self, stream: S) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into> + 'static, + E: Into + 'static, { let slf = match self.prep_for_sending() { Ok(slf) => slf, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 7e1bcd646..51fce1913 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,5 +1,4 @@ use std::{ - error::Error as StdError, future::Future, net, pin::Pin, @@ -9,12 +8,12 @@ use std::{ }; use actix_http::{ - body::{AnyBody, BodyStream}, + body::BodyStream, http::{ header::{self, HeaderMap, HeaderName, IntoHeaderValue}, Error as HttpError, }, - Error, RequestHead, RequestHeadType, + RequestHead, RequestHeadType, }; use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; @@ -26,8 +25,9 @@ use serde::Serialize; use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use crate::{ + any_body::AnyBody, error::{FreezeRequestError, InvalidUrl, SendRequestError}, - ClientConfig, ClientResponse, ConnectRequest, ConnectResponse, + BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse, }; #[derive(Debug, From)] @@ -162,12 +162,6 @@ impl From for SendClientRequest { } } -impl From for SendClientRequest { - fn from(e: Error) -> Self { - SendClientRequest::Err(Some(e.into())) - } -} - impl From for SendClientRequest { fn from(e: HttpError) -> Self { SendClientRequest::Err(Some(e.into())) @@ -236,7 +230,9 @@ impl RequestSender { response_decompress, timeout, config, - AnyBody::Bytes(Bytes::from(body)), + AnyBody::Bytes { + body: Bytes::from(body), + }, ) } @@ -265,7 +261,9 @@ impl RequestSender { response_decompress, timeout, config, - AnyBody::Bytes(Bytes::from(body)), + AnyBody::Bytes { + body: Bytes::from(body), + }, ) } @@ -279,7 +277,7 @@ impl RequestSender { ) -> SendClientRequest where S: Stream> + Unpin + 'static, - E: Into> + 'static, + E: Into + 'static, { self.send_body( addr, diff --git a/benches/responder.rs b/benches/responder.rs index 5d0b98d5f..20aae3351 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -1,9 +1,10 @@ use std::{future::Future, time::Instant}; +use actix_http::body::BoxBody; use actix_utils::future::{ready, Ready}; -use actix_web::http::StatusCode; -use actix_web::test::TestRequest; -use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; +use actix_web::{ + error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder, +}; use criterion::{criterion_group, criterion_main, Criterion}; use futures_util::future::{join_all, Either}; @@ -50,7 +51,9 @@ where } impl Responder for StringResponder { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = BoxBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self.0) @@ -58,9 +61,11 @@ impl Responder for StringResponder { } impl Responder for OptionResponder { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = BoxBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self.0 { - Some(t) => t.respond_to(req), + Some(t) => t.respond_to(req).map_into_boxed_body(), None => HttpResponse::from_error(error::ErrorInternalServerError("err")), } } diff --git a/src/app.rs b/src/app.rs index a291a959e..36063ec79 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,37 +1,35 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::rc::Rc; +use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; -use actix_http::body::{AnyBody, MessageBody}; -use actix_http::{Extensions, Request}; -use actix_service::boxed::{self, BoxServiceFactory}; +use actix_http::{ + body::{BoxBody, MessageBody}, + Extensions, Request, +}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, + apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, + Transform, }; use futures_util::future::FutureExt as _; -use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; -use crate::config::ServiceConfig; -use crate::data::{Data, DataFactory, FnDataFactory}; -use crate::dev::ResourceDef; -use crate::error::Error; -use crate::resource::Resource; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, +use crate::{ + app_service::{AppEntry, AppInit, AppRoutingFactory}, + config::ServiceConfig, + data::{Data, DataFactory, FnDataFactory}, + dev::ResourceDef, + error::Error, + resource::Resource, + route::Route, + service::{ + AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, + ServiceRequest, ServiceResponse, + }, }; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; - /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App { endpoint: T, services: Vec>, - default: Option>, + default: Option>, factory_ref: Rc>>, data_factories: Vec, external: Vec, @@ -39,7 +37,7 @@ pub struct App { _phantom: PhantomData, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -287,7 +285,7 @@ where /// ); /// } /// ``` - pub fn default_service(mut self, f: F) -> Self + pub fn default_service(mut self, svc: F) -> Self where F: IntoServiceFactory, U: ServiceFactory< @@ -298,10 +296,12 @@ where > + 'static, U::InitError: fmt::Debug, { - // create and configure default resource - self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( - |e| log::error!("Can not construct default service: {:?}", e), - )))); + let svc = svc + .into_factory() + .map(|res| res.map_into_boxed_body()) + .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)); + + self.default = Some(Rc::new(boxed::factory(svc))); self } diff --git a/src/app_service.rs b/src/app_service.rs index cf34b302e..bca8f2629 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -2,10 +2,7 @@ use std::{cell::RefCell, mem, rc::Rc}; use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Router, Url}; -use actix_service::{ - boxed::{self, BoxService, BoxServiceFactory}, - fn_service, Service, ServiceFactory, -}; +use actix_service::{boxed, fn_service, Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; @@ -15,13 +12,14 @@ use crate::{ guard::Guard, request::{HttpRequest, HttpRequestPool}, rmap::ResourceMap, - service::{AppServiceFactory, ServiceRequest, ServiceResponse}, + service::{ + AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest, + ServiceResponse, + }, Error, HttpResponse, }; type Guards = Vec>; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -39,7 +37,7 @@ where pub(crate) extensions: RefCell>, pub(crate) async_data_factories: Rc<[FnDataFactory]>, pub(crate) services: Rc>>>, - pub(crate) default: Option>, + pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, } @@ -230,8 +228,14 @@ where } pub struct AppRoutingFactory { - services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, - default: Rc, + services: Rc< + [( + ResourceDef, + BoxedHttpServiceFactory, + RefCell>, + )], + >, + default: Rc, } impl ServiceFactory for AppRoutingFactory { @@ -279,8 +283,8 @@ impl ServiceFactory for AppRoutingFactory { /// The Actix Web router default entry point. pub struct AppRouting { - router: Router, - default: HttpService, + router: Router, + default: BoxedHttpService, } impl Service for AppRouting { diff --git a/src/data.rs b/src/data.rs index d27ad196b..b29e4ecf4 100644 --- a/src/data.rs +++ b/src/data.rs @@ -284,7 +284,7 @@ mod tests { async fn test_data_from_arc() { let data_new = Data::new(String::from("test-123")); let data_from_arc = Data::from(Arc::new(String::from("test-123"))); - assert_eq!(data_new.0, data_from_arc.0) + assert_eq!(data_new.0, data_from_arc.0); } #[actix_rt::test] diff --git a/src/dev.rs b/src/dev.rs index 59805b822..d4a64985c 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -1,7 +1,7 @@ //! Lower-level types and re-exports. //! //! Most users will not have to interact with the types in this module, but it is useful for those -//! writing extractors, middleware and libraries, or interacting with the service API directly. +//! writing extractors, middleware, libraries, or interacting with the service API directly. pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] @@ -14,11 +14,6 @@ pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; -#[allow(deprecated)] -pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream}; - -#[cfg(feature = "__compress")] -pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::{Server, ServerHandle}; @@ -26,8 +21,10 @@ pub use actix_service::{ always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, }; +#[cfg(feature = "__compress")] +pub use actix_http::encoding::Decoder as Decompress; + use crate::http::header::ContentEncoding; -use actix_http::ResponseBuilder; use actix_router::Patterns; @@ -62,7 +59,7 @@ pub trait BodyEncoding { fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; } -impl BodyEncoding for ResponseBuilder { +impl BodyEncoding for actix_http::ResponseBuilder { fn get_encoding(&self) -> Option { self.extensions().get::().map(|enc| enc.0) } @@ -73,7 +70,7 @@ impl BodyEncoding for ResponseBuilder { } } -impl BodyEncoding for Response { +impl BodyEncoding for actix_http::Response { fn get_encoding(&self) -> Option { self.extensions().get::().map(|enc| enc.0) } @@ -105,3 +102,41 @@ impl BodyEncoding for crate::HttpResponse { self } } + +// TODO: remove this if it doesn't appear to be needed + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum AnyBody { + None, + Full { body: crate::web::Bytes }, + Boxed { body: actix_http::body::BoxBody }, +} + +impl crate::body::MessageBody for AnyBody { + type Error = crate::BoxError; + + /// Body size hint. + fn size(&self) -> crate::body::BodySize { + match self { + AnyBody::None => crate::body::BodySize::None, + AnyBody::Full { body } => body.size(), + AnyBody::Boxed { body } => body.size(), + } + } + + /// Attempt to pull out the next chunk of body bytes. + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll>> { + match self.get_mut() { + AnyBody::None => std::task::Poll::Ready(None), + AnyBody::Full { body } => { + let bytes = std::mem::take(body); + std::task::Poll::Ready(Some(Ok(bytes))) + } + AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx), + } + } +} diff --git a/src/error/error.rs b/src/error/error.rs index add290867..be17c1962 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -1,6 +1,6 @@ use std::{error::Error as StdError, fmt}; -use actix_http::{body::AnyBody, Response}; +use actix_http::{body::BoxBody, Response}; use crate::{HttpResponse, ResponseError}; @@ -69,8 +69,8 @@ impl From for Error { } } -impl From for Response { - fn from(err: Error) -> Response { +impl From for Response { + fn from(err: Error) -> Response { err.error_response().into() } } diff --git a/src/error/internal.rs b/src/error/internal.rs index 3d99012dc..c766ba83e 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -1,6 +1,10 @@ use std::{cell::RefCell, fmt, io::Write as _}; -use actix_http::{body::AnyBody, header, StatusCode}; +use actix_http::{ + body::BoxBody, + header::{self, IntoHeaderValue as _}, + StatusCode, +}; use bytes::{BufMut as _, BytesMut}; use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError}; @@ -84,11 +88,10 @@ where let mut buf = BytesMut::new().writer(); let _ = write!(buf, "{}", self); - res.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); - res.set_body(AnyBody::from(buf.into_inner())) + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + + res.set_body(BoxBody::new(buf.into_inner())) } InternalErrorType::Response(ref resp) => { @@ -106,7 +109,9 @@ impl Responder for InternalError where T: fmt::Debug + fmt::Display + 'static, { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = BoxBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from_error(self) } } diff --git a/src/error/macros.rs b/src/error/macros.rs index 38650c5e8..78b1ed9f6 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -97,7 +97,7 @@ mod tests { let resp_body: &mut dyn MB = &mut body; let body = resp_body.downcast_ref::().unwrap(); assert_eq!(body, "hello cast"); - let body = &mut resp_body.downcast_mut::().unwrap(); + let body = resp_body.downcast_mut::().unwrap(); body.push('!'); let body = resp_body.downcast_ref::().unwrap(); assert_eq!(body, "hello cast!"); diff --git a/src/error/response_error.rs b/src/error/response_error.rs index 2c34be3cb..7260efa1a 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -6,11 +6,17 @@ use std::{ io::{self, Write as _}, }; -use actix_http::{body::AnyBody, header, Response, StatusCode}; +use actix_http::{ + body::BoxBody, + header::{self, IntoHeaderValue}, + Response, StatusCode, +}; use bytes::BytesMut; -use crate::error::{downcast_dyn, downcast_get_type_id}; -use crate::{helpers, HttpResponse}; +use crate::{ + error::{downcast_dyn, downcast_get_type_id}, + helpers, HttpResponse, +}; /// Errors that can generate responses. // TODO: add std::error::Error bound when replacement for Box is found @@ -27,18 +33,16 @@ pub trait ResponseError: fmt::Debug + fmt::Display { /// /// By default, the generated response uses a 500 Internal Server Error status code, a /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl. - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> HttpResponse { let mut res = HttpResponse::new(self.status_code()); let mut buf = BytesMut::new(); let _ = write!(helpers::MutWriter(&mut buf), "{}", self); - res.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); + let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); - res.set_body(AnyBody::from(buf)) + res.set_body(BoxBody::new(buf)) } downcast_get_type_id!(); @@ -86,8 +90,8 @@ impl ResponseError for actix_http::Error { StatusCode::INTERNAL_SERVER_ERROR } - fn error_response(&self) -> HttpResponse { - HttpResponse::new(self.status_code()).set_body(self.to_string().into()) + fn error_response(&self) -> HttpResponse { + HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body() } } @@ -123,8 +127,8 @@ impl ResponseError for actix_http::error::ContentTypeError { } impl ResponseError for actix_http::ws::HandshakeError { - fn error_response(&self) -> HttpResponse { - Response::from(self).into() + fn error_response(&self) -> HttpResponse { + Response::from(self).map_into_boxed_body().into() } } diff --git a/src/handler.rs b/src/handler.rs index ddefe8d53..e543ecc7f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,21 +1,21 @@ use std::future::Future; -use actix_service::{ - boxed::{self, BoxServiceFactory}, - fn_service, -}; +use actix_service::{boxed, fn_service}; use crate::{ - service::{ServiceRequest, ServiceResponse}, - Error, FromRequest, HttpResponse, Responder, + body::MessageBody, + service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, + BoxError, FromRequest, HttpResponse, Responder, }; /// A request handler is an async function that accepts zero or more parameters that can be -/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type -/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait). +/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted +/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait). /// /// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not -/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information. +/// a valid handler. See for more information. +/// +/// [`impl FromRequest`]: crate::FromRequest pub trait Handler: Clone + 'static where R: Future, @@ -24,29 +24,44 @@ where fn call(&self, param: T) -> R; } -pub fn handler_service( - handler: F, -) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()> +pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory where F: Handler, T: FromRequest, R: Future, R::Output: Responder, + ::Body: MessageBody, + <::Body as MessageBody>::Error: Into, { boxed::factory(fn_service(move |req: ServiceRequest| { let handler = handler.clone(); + async move { let (req, mut payload) = req.into_parts(); + let res = match T::from_request(&req, &mut payload).await { Err(err) => HttpResponse::from_error(err), - Ok(data) => handler.call(data).await.respond_to(&req), + + Ok(data) => handler + .call(data) + .await + .respond_to(&req) + .map_into_boxed_body(), }; + Ok(ServiceResponse::new(req, res)) } })) } -/// FromRequest trait impl for tuples +/// Generates a [`Handler`] trait impl for N-ary functions where N is specified with a sequence of +/// space separated type parameters. +/// +/// # Examples +/// ```ignore +/// factory_tuple! {} // implements Handler for types: fn() -> Res +/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res +/// ``` macro_rules! factory_tuple ({ $($param:ident)* } => { impl Handler<($($param,)*), Res> for Func where Func: Fn($($param),*) -> Res + Clone + 'static, diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index fe291c011..70e4118cf 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -208,7 +208,7 @@ impl Accept { /// If no q-factors are provided, the first mime type is chosen. Note that items without /// q-factors are given the maximum preference value. /// - /// As per the spec, will return [`Mime::STAR_STAR`] (indicating no preference) if the contained + /// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained /// list is empty. /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 diff --git a/src/lib.rs b/src/lib.rs index 3ad77ff5f..f6ec4082a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,3 +115,5 @@ pub use crate::scope::Scope; pub use crate::server::HttpServer; // TODO: is exposing the error directly really needed pub use crate::types::{Either, EitherExtractError}; + +pub(crate) type BoxError = Box; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index a75335981..e6ef1806f 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -1,13 +1,12 @@ //! For middleware documentation, see [`Compat`]. use std::{ - error::Error as StdError, future::Future, pin::Pin, task::{Context, Poll}, }; -use actix_http::body::{AnyBody, MessageBody}; +use actix_http::body::MessageBody; use actix_service::{Service, Transform}; use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; @@ -123,10 +122,9 @@ pub trait MapServiceResponseBody { impl MapServiceResponseBody for ServiceResponse where B: MessageBody + Unpin + 'static, - B::Error: Into>, { fn map_body(self) -> ServiceResponse { - self.map_body(|_, body| AnyBody::new_boxed(body)) + self.map_into_boxed_body() } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 3b99fd6b3..d017e9a5a 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -10,14 +10,13 @@ use std::{ }; use actix_http::{ - body::{AnyBody, MessageBody}, + body::{EitherBody, MessageBody}, encoding::Encoder, http::header::{ContentEncoding, ACCEPT_ENCODING}, StatusCode, }; use actix_service::{Service, Transform}; use actix_utils::future::{ok, Either, Ready}; -use bytes::Bytes; use futures_core::ready; use once_cell::sync::Lazy; use pin_project_lite::pin_project; @@ -62,7 +61,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Response = ServiceResponse>>; + type Response = ServiceResponse>>; type Error = Error; type Transform = CompressMiddleware; type InitError = (); @@ -112,7 +111,7 @@ where S: Service, Error = Error>, B: MessageBody, { - type Response = ServiceResponse>>; + type Response = ServiceResponse>>; type Error = Error; type Future = Either, Ready>>; @@ -144,19 +143,15 @@ where // There is an HTTP header but we cannot match what client as asked for Some(Err(_)) => { - let res = HttpResponse::new(StatusCode::NOT_ACCEPTABLE); + let res = HttpResponse::with_body( + StatusCode::NOT_ACCEPTABLE, + SUPPORTED_ALGORITHM_NAMES.clone(), + ); - let res: HttpResponse>> = res.map_body(move |head, _| { - let body_bytes = Bytes::from(SUPPORTED_ALGORITHM_NAMES.as_bytes()); - - Encoder::response( - ContentEncoding::Identity, - head, - AnyBody::Bytes(body_bytes), - ) - }); - - Either::right(ok(req.into_response(res))) + Either::right(ok(req + .into_response(res) + .map_into_boxed_body() + .map_into_right_body())) } } } @@ -179,7 +174,7 @@ where B: MessageBody, S: Service, Error = Error>, { - type Output = Result>>, Error>; + type Output = Result>>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); @@ -193,10 +188,11 @@ where }; Poll::Ready(Ok(resp.map_body(move |head, body| { - Encoder::response(enc, head, AnyBody::Body(body)) + EitherBody::left(Encoder::response(enc, head, body)) }))) } - Err(e) => Poll::Ready(Err(e)), + + Err(err) => Poll::Ready(Err(err)), } } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 6ab16a4eb..f89b13a1c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -22,7 +22,7 @@ use regex::{Regex, RegexSet}; use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ - dev::{BodySize, MessageBody}, + body::{BodySize, MessageBody}, http::HeaderName, service::{ServiceRequest, ServiceResponse}, Error, HttpResponse, Result, diff --git a/src/resource.rs b/src/resource.rs index 851ce0fc9..fc417bac2 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,32 +1,29 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::rc::Rc; +use std::{cell::RefCell, fmt, future::Future, rc::Rc}; use actix_http::Extensions; use actix_router::{IntoPatterns, Patterns}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, + apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; use crate::{ + body::MessageBody, data::Data, - dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef}, + dev::{ensure_leading_slash, AppService, ResourceDef}, guard::Guard, handler::Handler, responder::Responder, route::{Route, RouteService}, - service::{ServiceRequest, ServiceResponse}, - Error, FromRequest, HttpResponse, + service::{ + BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, + ServiceResponse, + }, + BoxError, Error, FromRequest, HttpResponse, }; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; - /// *Resource* is an entry in resources table which corresponds to requested URL. /// /// Resource in turn has at least one route. @@ -56,7 +53,7 @@ pub struct Resource { routes: Vec, app_data: Option, guards: Vec>, - default: HttpNewService, + default: BoxedHttpServiceFactory, factory_ref: Rc>>, } @@ -242,6 +239,8 @@ where I: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody, + <::Body as MessageBody>::Error: Into, { self.routes.push(Route::new().to(handler)); self @@ -422,7 +421,7 @@ where pub struct ResourceFactory { routes: Vec, - default: HttpNewService, + default: BoxedHttpServiceFactory, } impl ServiceFactory for ResourceFactory { @@ -454,7 +453,7 @@ impl ServiceFactory for ResourceFactory { pub struct ResourceService { routes: Vec, - default: HttpService, + default: BoxedHttpService, } impl Service for ResourceService { diff --git a/src/responder.rs b/src/responder.rs index 8a84be598..9d8a0e8ed 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,19 +1,21 @@ use std::borrow::Cow; use actix_http::{ - body::AnyBody, + body::{BoxBody, EitherBody, MessageBody}, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; use bytes::{Bytes, BytesMut}; -use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// Trait implemented by types that can be converted to an HTTP response. /// /// Any types that implement this trait can be used in the return type of a handler. pub trait Responder { + type Body: MessageBody + 'static; + /// Convert self to `HttpResponse`. - fn respond_to(self, req: &HttpRequest) -> HttpResponse; + fn respond_to(self, req: &HttpRequest) -> HttpResponse; /// Override a status code for a Responder. /// @@ -59,38 +61,52 @@ pub trait Responder { } impl Responder for HttpResponse { + type Body = BoxBody; + #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { self } } -impl Responder for actix_http::Response { +impl Responder for actix_http::Response { + type Body = BoxBody; + #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from(self) } } impl Responder for HttpResponseBuilder { + type Body = BoxBody; + #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { self.finish() } } impl Responder for actix_http::ResponseBuilder { + type Body = BoxBody; + #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - HttpResponse::from(self.finish()) + fn respond_to(mut self, req: &HttpRequest) -> HttpResponse { + self.finish().map_into_boxed_body().respond_to(req) } } -impl Responder for Option { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { +impl Responder for Option +where + T: Responder, + ::Error: Into, +{ + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Some(val) => val.respond_to(req), - None => HttpResponse::new(StatusCode::NOT_FOUND), + Some(val) => val.respond_to(req).map_into_left_body(), + None => HttpResponse::new(StatusCode::NOT_FOUND).map_into_right_body(), } } } @@ -98,47 +114,69 @@ impl Responder for Option { impl Responder for Result where T: Responder, + ::Error: Into, E: Into, { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Ok(val) => val.respond_to(req), - Err(e) => HttpResponse::from_error(e.into()), + Ok(val) => val.respond_to(req).map_into_left_body(), + Err(err) => HttpResponse::from_error(err.into()).map_into_right_body(), } } } impl Responder for (T, StatusCode) { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = T::Body; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); *res.status_mut() = self.1; res } } -macro_rules! impl_responder { - ($res: ty, $ct: path) => { +macro_rules! impl_responder_by_forward_into_base_response { + ($res:ty, $body:ty) => { impl Responder for $res { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok().content_type($ct).body(self) + type Body = $body; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + let res: actix_http::Response<_> = self.into(); + res.into() + } + } + }; + + ($res:ty) => { + impl_responder_by_forward_into_base_response!($res, $res); + }; +} + +impl_responder_by_forward_into_base_response!(&'static [u8]); +impl_responder_by_forward_into_base_response!(Bytes); +impl_responder_by_forward_into_base_response!(BytesMut); + +impl_responder_by_forward_into_base_response!(&'static str); +impl_responder_by_forward_into_base_response!(String); + +macro_rules! impl_into_string_responder { + ($res:ty) => { + impl Responder for $res { + type Body = String; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + let string: String = self.into(); + let res: actix_http::Response<_> = string.into(); + res.into() } } }; } -impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8); - -impl_responder!(String, mime::TEXT_PLAIN_UTF_8); - -impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8); - -impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8); - -impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM); - -impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM); - -impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM); +impl_into_string_responder!(&'_ String); +impl_into_string_responder!(Cow<'_, str>); /// Allows overriding status code and headers for a responder. pub struct CustomResponder { @@ -204,11 +242,17 @@ impl CustomResponder { } } -impl Responder for CustomResponder { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { +impl Responder for CustomResponder +where + T: Responder, + ::Error: Into, +{ + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let headers = match self.headers { Ok(headers) => headers, - Err(err) => return HttpResponse::from_error(Error::from(err)), + Err(err) => return HttpResponse::from_error(err).map_into_right_body(), }; let mut res = self.responder.respond_to(req); @@ -222,7 +266,7 @@ impl Responder for CustomResponder { res.headers_mut().insert(k, v); } - res + res.map_into_left_body() } } @@ -231,11 +275,15 @@ pub(crate) mod tests { use actix_service::Service; use bytes::{Bytes, BytesMut}; + use actix_http::body::to_bytes; + use super::*; - use crate::dev::AnyBody; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{init_service, TestRequest}; - use crate::{error, web, App}; + use crate::{ + error, + http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + test::{assert_body_eq, init_service, TestRequest}, + web, App, + }; #[actix_rt::test] async fn test_option_responder() { @@ -253,112 +301,116 @@ pub(crate) mod tests { let req = TestRequest::with_uri("/some").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - match resp.response().body() { - AnyBody::Bytes(ref b) => { - let bytes = b.clone(); - assert_eq!(bytes, Bytes::from_static(b"some")); - } - _ => panic!(), - } - } - - pub(crate) trait BodyTest { - fn bin_ref(&self) -> &[u8]; - fn body(&self) -> &AnyBody; - } - - impl BodyTest for AnyBody { - fn bin_ref(&self) -> &[u8] { - match self { - AnyBody::Bytes(ref bin) => bin, - _ => unreachable!("bug in test impl"), - } - } - fn body(&self) -> &AnyBody { - self - } + assert_body_eq!(resp, b"some"); } #[actix_rt::test] async fn test_responder() { let req = TestRequest::default().to_http_request(); - let resp = "test".respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); + let res = "test".respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - - let resp = b"test".respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = b"test".respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - - let resp = "test".to_string().respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), ); - let resp = (&"test".to_string()).respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); + let res = "test".to_string().respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = (&"test".to_string()).respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); let s = String::from("test"); - let resp = Cow::Borrowed(s.as_str()).respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); + let res = Cow::Borrowed(s.as_str()).respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - - let resp = Cow::<'_, str>::Owned(s).respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), ); - let resp = Cow::Borrowed("test").respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); + let res = Cow::<'_, str>::Owned(s).respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); - - let resp = Bytes::from_static(b"test").respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = Cow::Borrowed("test").respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = Bytes::from_static(b"test").respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); - - let resp = BytesMut::from(b"test".as_ref()).respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = BytesMut::from(b"test".as_ref()).respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); // InternalError - let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let res = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] @@ -368,11 +420,14 @@ pub(crate) mod tests { // Result let resp = Ok::<_, Error>("test".to_string()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("text/plain; charset=utf-8") ); + assert_eq!( + to_bytes(resp.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); let res = Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) .respond_to(&req); @@ -389,7 +444,10 @@ pub(crate) mod tests { .respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); let res = "test" .to_string() @@ -397,11 +455,14 @@ pub(crate) mod tests { .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") ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); } #[actix_rt::test] @@ -409,17 +470,23 @@ pub(crate) mod tests { let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::OK) .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("application/json") ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); } } diff --git a/src/response/builder.rs b/src/response/builder.rs index e61f7e16f..b5bef2e99 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -1,14 +1,13 @@ use std::{ cell::{Ref, RefMut}, convert::TryInto, - error::Error as StdError, future::Future, pin::Pin, task::{Context, Poll}, }; use actix_http::{ - body::{AnyBody, BodyStream}, + body::{BodyStream, BoxBody, MessageBody}, http::{ header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, ConnectionType, Error as HttpError, StatusCode, @@ -26,14 +25,14 @@ use cookie::{Cookie, CookieJar}; use crate::{ error::{Error, JsonPayloadError}, - HttpResponse, + BoxError, HttpResponse, }; /// An HTTP response builder. /// /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct HttpResponseBuilder { - res: Option>, + res: Option>, err: Option, #[cfg(feature = "cookies")] cookies: Option, @@ -44,7 +43,7 @@ impl HttpResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { Self { - res: Some(Response::new(status)), + res: Some(Response::with_body(status, BoxBody::new(()))), err: None, #[cfg(feature = "cookies")] cookies: None, @@ -299,7 +298,6 @@ impl HttpResponseBuilder { } /// Mutable reference to a the response's extensions - #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { self.res .as_mut() @@ -307,18 +305,20 @@ impl HttpResponseBuilder { .extensions_mut() } - /// Set a body and generate `Response`. + /// Set a body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. - #[inline] - pub fn body>(&mut self, body: B) -> HttpResponse { - match self.message_body(body.into()) { - Ok(res) => res, + pub fn body(&mut self, body: B) -> HttpResponse + where + B: MessageBody + 'static, + { + match self.message_body(body) { + Ok(res) => res.map_into_boxed_body(), Err(err) => HttpResponse::from_error(err), } } - /// Set a body and generate `Response`. + /// Set a body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> Result, Error> { @@ -332,7 +332,7 @@ impl HttpResponseBuilder { .expect("cannot reuse response builder") .set_body(body); - #[allow(unused_mut)] + #[allow(unused_mut)] // mut is only unused when cookies are disabled let mut res = HttpResponse::from(res); #[cfg(feature = "cookies")] @@ -348,19 +348,19 @@ impl HttpResponseBuilder { Ok(res) } - /// Set a streaming body and generate `Response`. + /// Set a streaming body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn streaming(&mut self, stream: S) -> HttpResponse where S: Stream> + 'static, - E: Into> + 'static, + E: Into + 'static, { - self.body(AnyBody::new_boxed(BodyStream::new(stream))) + self.body(BodyStream::new(stream)) } - /// Set a json body and generate `Response` + /// Set a JSON body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: impl Serialize) -> HttpResponse { @@ -376,18 +376,18 @@ impl HttpResponseBuilder { self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); } - self.body(AnyBody::from(body)) + self.body(body) } Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)), } } - /// Set an empty body and generate `Response` + /// Set an empty body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. #[inline] pub fn finish(&mut self) -> HttpResponse { - self.body(AnyBody::empty()) + self.body(()) } /// This method construct new `HttpResponseBuilder` @@ -416,7 +416,7 @@ impl From for HttpResponse { } } -impl From for Response { +impl From for Response { fn from(mut builder: HttpResponseBuilder) -> Self { builder.finish().into() } @@ -435,12 +435,9 @@ mod tests { use actix_http::body; use super::*; - use crate::{ - dev::AnyBody, - http::{ - header::{self, HeaderValue, CONTENT_TYPE}, - StatusCode, - }, + use crate::http::{ + header::{self, HeaderValue, CONTENT_TYPE}, + StatusCode, }; #[test] @@ -475,7 +472,7 @@ mod tests { fn test_content_type() { let resp = HttpResponseBuilder::new(StatusCode::OK) .content_type("text/plain") - .body(AnyBody::empty()); + .body(Bytes::new()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } diff --git a/src/response/response.rs b/src/response/response.rs index 23562ab0e..97de21e42 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, EitherBody, MessageBody}, http::{header::HeaderMap, StatusCode}, Extensions, Response, ResponseHead, }; @@ -25,12 +25,12 @@ use { use crate::{error::Error, HttpResponseBuilder}; /// An outgoing response. -pub struct HttpResponse { +pub struct HttpResponse { res: Response, pub(crate) error: Option, } -impl HttpResponse { +impl HttpResponse { /// Constructs a response. #[inline] pub fn new(status: StatusCode) -> Self { @@ -227,8 +227,26 @@ impl HttpResponse { } } - // TODO: into_body equivalent - // TODO: into_boxed_body + // TODO: docs for the body map methods below + + #[inline] + pub fn map_into_left_body(self) -> HttpResponse> { + self.map_body(|_, body| EitherBody::left(body)) + } + + #[inline] + pub fn map_into_right_body(self) -> HttpResponse> { + self.map_body(|_, body| EitherBody::right(body)) + } + + #[inline] + pub fn map_into_boxed_body(self) -> HttpResponse + where + B: MessageBody + 'static, + { + // TODO: avoid double boxing with down-casting, if it improves perf + self.map_body(|_, body| BoxBody::new(body)) + } /// Extract response body pub fn into_body(self) -> B { @@ -273,14 +291,14 @@ impl From> for Response { } } -// Future is only implemented for AnyBody payload type because it's the most useful for making +// Future is only implemented for BoxBody payload type because it's the most useful for making // simple handlers without async blocks. Making it generic over all MessageBody types requires a // future impl on Response which would cause it's body field to be, undesirably, Option. // // This impl is not particularly efficient due to the Response construction and should probably // not be invoked if performance is important. Prefer an async fn/block in such cases. -impl Future for HttpResponse { - type Output = Result, Error>; +impl Future for HttpResponse { + type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if let Some(err) = self.error.take() { diff --git a/src/route.rs b/src/route.rs index 0c0699430..1eb323068 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,19 +1,18 @@ -#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`) - -use std::{future::Future, rc::Rc}; +use std::{future::Future, mem, rc::Rc}; use actix_http::http::Method; use actix_service::{ - boxed::{self, BoxService, BoxServiceFactory}, - Service, ServiceFactory, ServiceFactoryExt, + boxed::{self, BoxService}, + fn_service, Service, ServiceFactory, ServiceFactoryExt, }; use futures_core::future::LocalBoxFuture; use crate::{ + body::MessageBody, guard::{self, Guard}, handler::{handler_service, Handler}, - service::{ServiceRequest, ServiceResponse}, - Error, FromRequest, HttpResponse, Responder, + service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, + BoxError, Error, FromRequest, HttpResponse, Responder, }; /// Resource route definition @@ -21,7 +20,7 @@ use crate::{ /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { - service: BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>, + service: BoxedHttpServiceFactory, guards: Rc>>, } @@ -30,13 +29,15 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: handler_service(HttpResponse::NotFound), + service: boxed::factory(fn_service(|req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::NotFound())) + })), guards: Rc::new(Vec::new()), } } pub(crate) fn take_guards(&mut self) -> Vec> { - std::mem::take(Rc::get_mut(&mut self.guards).unwrap()) + mem::take(Rc::get_mut(&mut self.guards).unwrap()) } } @@ -181,6 +182,8 @@ impl Route { T: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody, + <::Body as MessageBody>::Error: Into, { self.service = handler_service(handler); self diff --git a/src/scope.rs b/src/scope.rs index c20b5d7c8..ff013671b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,9 +3,8 @@ use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; use actix_http::Extensions; use actix_router::{ResourceDef, Router}; use actix_service::{ - apply, apply_fn_factory, - boxed::{self, BoxService, BoxServiceFactory}, - IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, + apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; @@ -13,16 +12,17 @@ use futures_util::future::join_all; use crate::{ config::ServiceConfig, data::Data, - dev::{AppService, HttpServiceFactory}, + dev::AppService, guard::Guard, rmap::ResourceMap, - service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse}, + service::{ + AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, + ServiceFactoryWrapper, ServiceRequest, ServiceResponse, + }, Error, Resource, Route, }; type Guards = Vec>; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Resources scope. /// @@ -58,7 +58,7 @@ pub struct Scope { app_data: Option, services: Vec>, guards: Vec>, - default: Option>, + default: Option>, external: Vec, factory_ref: Rc>>, } @@ -470,8 +470,14 @@ where } pub struct ScopeFactory { - services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, - default: Rc, + services: Rc< + [( + ResourceDef, + BoxedHttpServiceFactory, + RefCell>, + )], + >, + default: Rc, } impl ServiceFactory for ScopeFactory { @@ -518,8 +524,8 @@ impl ServiceFactory for ScopeFactory { } pub struct ScopeService { - router: Router>>, - default: HttpService, + router: Router>>, + default: BoxedHttpService, } impl Service for ScopeService { @@ -580,12 +586,11 @@ mod tests { use bytes::Bytes; use crate::{ - dev::AnyBody, guard, http::{header, HeaderValue, Method, StatusCode}, middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, - test::{call_service, init_service, read_body, TestRequest}, + test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, web, App, HttpMessage, HttpRequest, HttpResponse, }; @@ -748,20 +753,13 @@ mod tests { .await; let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - match resp.response().body() { - AnyBody::Bytes(ref b) => { - let bytes = b.clone(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } + let res = srv.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_body_eq!(res, b"project: project1"); let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let res = srv.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] @@ -849,16 +847,9 @@ mod tests { .await; let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - AnyBody::Bytes(ref b) => { - let bytes = b.clone(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } + let res = srv.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::CREATED); + assert_body_eq!(res, b"project: project_1"); } #[actix_rt::test] @@ -877,20 +868,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - AnyBody::Bytes(ref b) => { - let bytes = b.clone(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } + let res = srv.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::CREATED); + assert_body_eq!(res, b"project: test - 1"); let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let res = srv.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] diff --git a/src/server.rs b/src/server.rs index 1bf56655b..3db849410 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,6 @@ use std::{ any::Any, - cmp, - error::Error as StdError, - fmt, io, + cmp, fmt, io, marker::PhantomData, net, sync::{Arc, Mutex}, @@ -75,15 +73,13 @@ where I: IntoServiceFactory, S: ServiceFactory + 'static, - // S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, S::Service: 'static, - // S::Service: 'static, + B: MessageBody + 'static, - B::Error: Into>, { /// Create new HTTP server with application factory pub fn new(factory: F) -> Self { @@ -656,8 +652,8 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result io::Result { builder.set_alpn_select_callback(|_, protocols| { const H2: &[u8] = b"\x02h2"; diff --git a/src/service.rs b/src/service.rs index 8ba38df43..df9e809e4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,14 +1,19 @@ -use std::cell::{Ref, RefMut}; -use std::rc::Rc; -use std::{fmt, net}; +use std::{ + cell::{Ref, RefMut}, + fmt, net, + rc::Rc, +}; use actix_http::{ - body::{AnyBody, MessageBody}, + body::{BoxBody, EitherBody, MessageBody}, http::{HeaderMap, Method, StatusCode, Uri, Version}, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url}; -use actix_service::{IntoServiceFactory, ServiceFactory}; +use actix_service::{ + boxed::{BoxService, BoxServiceFactory}, + IntoServiceFactory, ServiceFactory, +}; #[cfg(feature = "cookies")] use cookie::{Cookie, ParseError as CookieParseError}; @@ -21,6 +26,10 @@ use crate::{ Error, HttpRequest, HttpResponse, }; +pub(crate) type BoxedHttpService = BoxService, Error>; +pub(crate) type BoxedHttpServiceFactory = + BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; + pub trait HttpServiceFactory { fn register(self, config: &mut AppService); } @@ -326,12 +335,12 @@ impl fmt::Debug for ServiceRequest { } /// A service level response wrapper. -pub struct ServiceResponse { +pub struct ServiceResponse { request: HttpRequest, response: HttpResponse, } -impl ServiceResponse { +impl ServiceResponse { /// Create service response from the error pub fn from_err>(err: E, request: HttpRequest) -> Self { let response = HttpResponse::from_error(err); @@ -401,6 +410,7 @@ impl ServiceResponse { impl ServiceResponse { /// Set a new body + #[inline] pub fn map_body(self, f: F) -> ServiceResponse where F: FnOnce(&mut ResponseHead, B) -> B2, @@ -412,6 +422,24 @@ impl ServiceResponse { request: self.request, } } + + #[inline] + pub fn map_into_left_body(self) -> ServiceResponse> { + self.map_body(|_, body| EitherBody::left(body)) + } + + #[inline] + pub fn map_into_right_body(self) -> ServiceResponse> { + self.map_body(|_, body| EitherBody::right(body)) + } + + #[inline] + pub fn map_into_boxed_body(self) -> ServiceResponse + where + B: MessageBody + 'static, + { + self.map_body(|_, body| BoxBody::new(body)) + } } impl From> for HttpResponse { diff --git a/src/test.rs b/src/test.rs index 77765e267..2cd01039d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,7 +4,6 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - body, http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, test::TestRequest as HttpTestRequest, Extensions, Request, @@ -20,9 +19,10 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::cookie::{Cookie, CookieJar}; use crate::{ app_service::AppInitServiceState, + body::{self, BoxBody, MessageBody}, config::AppConfig, data::Data, - dev::{AnyBody, MessageBody, Payload}, + dev::Payload, http::header::ContentType, rmap::ResourceMap, service::{ServiceRequest, ServiceResponse}, @@ -32,14 +32,14 @@ use crate::{ /// Create service that always responds with `HttpResponse::Ok()` and no body. pub fn ok_service( -) -> impl Service, Error = Error> { +) -> impl Service, Error = Error> { default_service(StatusCode::OK) } /// Create service that always responds with given status code and no body. pub fn default_service( status_code: StatusCode, -) -> impl Service, Error = Error> { +) -> impl Service, Error = Error> { (move |req: ServiceRequest| { ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) }) @@ -632,6 +632,22 @@ impl TestRequest { } } +/// Reduces boilerplate code when testing expected response payloads. +#[cfg(test)] +macro_rules! assert_body_eq { + ($res:ident, $expected:expr) => { + assert_eq!( + ::actix_http::body::to_bytes($res.into_body()) + .await + .expect("body read should have succeeded"), + Bytes::from_static($expected), + ) + }; +} + +#[cfg(test)] +pub(crate) use assert_body_eq; + #[cfg(test)] mod tests { use std::time::SystemTime; diff --git a/src/types/either.rs b/src/types/either.rs index 0a8a90133..3c759736e 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -12,7 +12,7 @@ use futures_core::ready; use pin_project_lite::pin_project; use crate::{ - dev, + body, dev, web::{Form, Json}, Error, FromRequest, HttpRequest, HttpResponse, Responder, }; @@ -146,10 +146,12 @@ where L: Responder, R: Responder, { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = body::EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Either::Left(a) => a.respond_to(req), - Either::Right(b) => b.respond_to(req), + Either::Left(a) => a.respond_to(req).map_into_left_body(), + Either::Right(b) => b.respond_to(req).map_into_right_body(), } } } diff --git a/src/types/form.rs b/src/types/form.rs index 098a864de..9c09c6b73 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -20,8 +20,9 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ - error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error, - HttpMessage, HttpRequest, HttpResponse, Responder, + body::EitherBody, error::UrlencodedError, extract::FromRequest, + http::header::CONTENT_LENGTH, web, Error, HttpMessage, HttpRequest, HttpResponse, + Responder, }; /// URL encoded payload extractor and responder. @@ -180,12 +181,21 @@ impl fmt::Display for Form { /// See [here](#responder) for example of usage as a handler return type. impl Responder for Form { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_urlencoded::to_string(&self.0) { - Ok(body) => HttpResponse::Ok() + Ok(body) => match HttpResponse::Ok() .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) - .body(body), - Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)), + .message_body(body) + { + Ok(res) => res.map_into_left_body(), + Err(err) => HttpResponse::from_error(err).map_into_right_body(), + }, + + Err(err) => { + HttpResponse::from_error(UrlencodedError::Serialize(err)).map_into_right_body() + } } } } @@ -408,11 +418,14 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::http::{ - header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, - StatusCode, - }; use crate::test::TestRequest; + use crate::{ + http::{ + header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + StatusCode, + }, + test::assert_body_eq, + }; #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Info { @@ -520,15 +533,13 @@ mod tests { hello: "world".to_string(), counter: 123, }); - let resp = form.respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); + let res = form.respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), + res.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/x-www-form-urlencoded") ); - - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); + assert_body_eq!(res, b"hello=world&counter=123"); } #[actix_rt::test] diff --git a/src/types/json.rs b/src/types/json.rs index 6d07fe45a..2b4d220e2 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -19,6 +19,7 @@ use actix_http::Payload; #[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ + body::EitherBody, error::{Error, JsonPayloadError}, extract::FromRequest, http::header::CONTENT_LENGTH, @@ -116,12 +117,21 @@ impl Serialize for Json { /// /// If serialization failed impl Responder for Json { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_json::to_string(&self.0) { - Ok(body) => HttpResponse::Ok() + Ok(body) => match HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) - .body(body), - Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)), + .message_body(body) + { + Ok(res) => res.map_into_left_body(), + Err(err) => HttpResponse::from_error(err).map_into_right_body(), + }, + + Err(err) => { + HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body() + } } } } @@ -444,7 +454,7 @@ mod tests { header::{self, CONTENT_LENGTH, CONTENT_TYPE}, StatusCode, }, - test::{load_body, TestRequest}, + test::{assert_body_eq, load_body, TestRequest}, }; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -472,15 +482,13 @@ mod tests { let j = Json(MyObject { name: "test".to_string(), }); - let resp = j.respond_to(&req); - assert_eq!(resp.status(), StatusCode::OK); + let res = j.respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), + res.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("application/json") ); - - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); + assert_body_eq!(res, b"{\"name\":\"test\"}"); } #[actix_rt::test] diff --git a/src/types/path.rs b/src/types/path.rs index cd24deb81..4b60d27c0 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -90,7 +90,7 @@ impl fmt::Display for Path { } } -/// See [here](#usage) for example of usage as an extractor. +/// See [here](#Examples) for example of usage as an extractor. impl FromRequest for Path where T: de::DeserializeOwned, diff --git a/src/types/payload.rs b/src/types/payload.rs index 00047e8b1..73987def5 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -43,12 +43,12 @@ use crate::{ /// Ok(format!("Request Body Bytes:\n{:?}", bytes)) /// } /// ``` -pub struct Payload(crate::dev::Payload); +pub struct Payload(dev::Payload); impl Payload { /// Unwrap to inner Payload type. #[inline] - pub fn into_inner(self) -> crate::dev::Payload { + pub fn into_inner(self) -> dev::Payload { self.0 } } @@ -62,7 +62,7 @@ impl Stream for Payload { } } -/// See [here](#usage) for example of usage as an extractor. +/// See [here](#Examples) for example of usage as an extractor. impl FromRequest for Payload { type Error = Error; type Future = Ready>; diff --git a/src/types/query.rs b/src/types/query.rs index ba2034bfc..9fac21173 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -105,7 +105,7 @@ impl fmt::Display for Query { } } -/// See [here](#usage) for example of usage as an extractor. +/// See [here](#Examples) for example of usage as an extractor. impl FromRequest for Query { type Error = Error; type Future = Ready>; diff --git a/src/web.rs b/src/web.rs index e9f5c8518..b58adc2f8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,14 +1,14 @@ //! Essentials helper functions and types for application registration. -use std::future::Future; +use std::{error::Error as StdError, future::Future}; use actix_http::http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::{ - error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, - responder::Responder, route::Route, scope::Scope, service::WebService, + body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler, + resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService, }; pub use crate::config::ServiceConfig; @@ -145,6 +145,8 @@ where I: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody + 'static, + <::Body as MessageBody>::Error: Into>, { Route::new().to(handler) } diff --git a/tests/test_server.rs b/tests/test_server.rs index 3f0fbfccc..a850f228d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -200,13 +200,10 @@ async fn test_body_encoding_override() { .body(STR) }))) .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::AnyBody::Bytes(STR.into()); let mut response = - HttpResponse::with_body(actix_web::http::StatusCode::OK, body); - + HttpResponse::with_body(actix_web::http::StatusCode::OK, STR); response.encoding(ContentEncoding::Deflate); - - response + response.map_into_boxed_body() }))) }); From fa7f3e6908cea673ed64bfc686af697436db1911 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 4 Dec 2021 19:41:15 +0000 Subject: [PATCH 138/861] undeprecate `App::data_factory` (#2484) --- CHANGES.md | 2 ++ src/app.rs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2dc45c3ed..2b5a71001 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_preference => preference}`. [#2480] +* Un-deprecate `App::data_factory`. [#2484] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] @@ -19,6 +20,7 @@ [#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 +[#2484]: https://github.com/actix/actix-web/pull/2484 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/app.rs b/src/app.rs index 36063ec79..efc108cb9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -140,10 +140,6 @@ where /// Add application data factory. This function is similar to `.data()` but it accepts a /// "data factory". Data values are constructed asynchronously during application /// initialization, before the server starts accepting requests. - #[deprecated( - since = "4.0.0", - note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead." - )] pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, From 4c9ca7196dab4c745024be19c600630a76388ee4 Mon Sep 17 00:00:00 2001 From: Mohammed Sazid Al Rashid Date: Sun, 5 Dec 2021 04:32:44 +0600 Subject: [PATCH 139/861] Add `WsResponseBuilder` to build web socket session response (#1920) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 4 + actix-http/src/ws/codec.rs | 12 +- actix-web-actors/CHANGES.md | 4 + actix-web-actors/src/ws.rs | 256 ++++++++++++++++++++++++++---- actix-web-actors/tests/test_ws.rs | 201 +++++++++++++++-------- 5 files changed, 379 insertions(+), 98 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 23c15296a..773f1ff39 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -9,6 +9,8 @@ * `body::None` struct. [#2468] * Impl `MessageBody` for `bytestring::ByteString`. [#2468] * `impl Clone for ws::HandshakeError`. [#2468] +* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +* `impl Default ` for `ws::Codec`. [#1920] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -24,9 +26,11 @@ * `impl Future` for `ResponseBuilder`. [#2468] * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +* `impl Copy` for `ws::Codec`. [#1920] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 +[#1920]: https://github.com/actix/actix-web/pull/1920 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 8655216fa..d80613e5f 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -63,8 +63,8 @@ pub enum Item { Last(Bytes), } -#[derive(Debug, Copy, Clone)] /// WebSocket protocol codec. +#[derive(Debug, Clone)] pub struct Codec { flags: Flags, max_size: usize, @@ -89,7 +89,8 @@ impl Codec { /// Set max frame size. /// - /// By default max size is set to 64kB. + /// By default max size is set to 64KiB. + #[must_use = "This returns the a new Codec, without modifying the original."] pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self @@ -98,12 +99,19 @@ impl Codec { /// Set decoder to client mode. /// /// By default decoder works in server mode. + #[must_use = "This returns the a new Codec, without modifying the original."] pub fn client_mode(mut self) -> Self { self.flags.remove(Flags::SERVER); self } } +impl Default for Codec { + fn default() -> Self { + Self::new() + } +} + impl Encoder for Codec { type Error = ProtocolError; diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index e3693f0f6..898098ed8 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx +* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] * Minimum supported Rust version (MSRV) is now 1.52. +[#1920]: https://github.com/actix/actix-web/pull/1920 + ## 4.0.0-beta.7 - 2021-09-09 * Minimum supported Rust version (MSRV) is now 1.51. diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index b6323c2ed..c41268b01 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,20 +1,24 @@ //! Websocket integration. -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, convert::TryFrom}; - -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope, +use std::{ + collections::VecDeque, + convert::TryFrom, + future::Future, + io, mem, + pin::Pin, + task::{Context, Poll}, }; -use actix::fut::ActorFuture; + use actix::{ + dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, + ToEnvelope, + }, + fut::ActorFuture, Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, SpawnHandle, }; -use actix_codec::{Decoder, Encoder}; +use actix_codec::{Decoder as _, Encoder as _}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; @@ -31,9 +35,188 @@ use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::Stream; use pin_project_lite::pin_project; -use tokio::sync::oneshot::Sender; +use tokio::sync::oneshot; + +/// Builder for Websocket session response. +/// +/// # Examples +/// +/// Create a Websocket session response with default configuration. +/// ```ignore +/// WsResponseBuilder::new(WsActor, &req, stream).start() +/// ``` +/// +/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols. +/// ```ignore +/// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB +/// +/// ws::WsResponseBuilder::new(WsActor, &req, stream) +/// .codec(Codec::new()) +/// .protocols(&["A", "B"]) +/// .frame_size(MAX_FRAME_SIZE) +/// .start() +/// ``` +pub struct WsResponseBuilder<'a, A, T> +where + A: Actor> + StreamHandler>, + T: Stream> + 'static, +{ + actor: A, + req: &'a HttpRequest, + stream: T, + codec: Option, + protocols: Option<&'a [&'a str]>, + frame_size: Option, +} + +impl<'a, A, T> WsResponseBuilder<'a, A, T> +where + A: Actor> + StreamHandler>, + T: Stream> + 'static, +{ + /// Construct a new `WsResponseBuilder` with actor, request, and payload stream. + /// + /// For usage example, see docs on [`WsResponseBuilder`] struct. + pub fn new(actor: A, req: &'a HttpRequest, stream: T) -> Self { + WsResponseBuilder { + actor, + req, + stream, + codec: None, + protocols: None, + frame_size: None, + } + } + + /// Set the protocols for the session. + pub fn protocols(mut self, protocols: &'a [&'a str]) -> Self { + self.protocols = Some(protocols); + self + } + + /// Set the max frame size for each message (in bytes). + /// + /// **Note**: This will override any given [`Codec`]'s max frame size. + pub fn frame_size(mut self, frame_size: usize) -> Self { + self.frame_size = Some(frame_size); + self + } + + /// Set the [`Codec`] for the session. If [`Self::frame_size`] is also set, the given + /// [`Codec`]'s max frame size will be overridden. + pub fn codec(mut self, codec: Codec) -> Self { + self.codec = Some(codec); + self + } + + fn handshake_resp(&self) -> Result { + match self.protocols { + Some(protocols) => handshake_with_protocols(self.req, protocols), + None => handshake(self.req), + } + } + + fn set_frame_size(&mut self) { + if let Some(frame_size) = self.frame_size { + match &mut self.codec { + Some(codec) => { + // modify existing codec's max frame size + let orig_codec = mem::take(codec); + *codec = orig_codec.max_size(frame_size); + } + + None => { + // create a new codec with the given size + self.codec = Some(Codec::new().max_size(frame_size)); + } + } + } + } + + /// Create a new Websocket context from an actor, request stream, and codec. + /// + /// Returns a pair, where the first item is an addr for the created actor, and the second item + /// is a stream intended to be set as part of the response + /// via [`HttpResponseBuilder::streaming()`]. + fn create_with_codec_addr( + actor: A, + stream: S, + codec: Codec, + ) -> (Addr
, impl Stream>) + where + A: StreamHandler>, + S: Stream> + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + messages: VecDeque::new(), + }; + ctx.add_stream(WsStream::new(stream, codec.clone())); + + let addr = ctx.address(); + + (addr, WebsocketContextFut::new(ctx, actor, mb, codec)) + } + + /// Perform WebSocket handshake and start actor. + /// + /// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change. + /// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a + /// stream of the body request. + /// + /// If there is a problem with the handshake, an error is returned. + /// + /// If successful, consume the [`WsResponseBuilder`] and return a [`HttpResponse`] wrapped in + /// a [`Result`]. + pub fn start(mut self) -> Result { + let mut res = self.handshake_resp()?; + self.set_frame_size(); + + match self.codec { + Some(codec) => { + let out_stream = WebsocketContext::with_codec(self.actor, self.stream, codec); + Ok(res.streaming(out_stream)) + } + None => { + let out_stream = WebsocketContext::create(self.actor, self.stream); + Ok(res.streaming(out_stream)) + } + } + } + + /// Perform WebSocket handshake and start actor. + /// + /// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change. + /// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a + /// stream of the body request. + /// + /// If there is a problem with the handshake, an error is returned. + /// + /// If successful, returns a pair where the first item is an address for the created actor and + /// the second item is the [`HttpResponse`] that should be returned from the websocket request. + pub fn start_with_addr(mut self) -> Result<(Addr, HttpResponse), Error> { + let mut res = self.handshake_resp()?; + self.set_frame_size(); + + match self.codec { + Some(codec) => { + let (addr, out_stream) = + Self::create_with_codec_addr(self.actor, self.stream, codec); + Ok((addr, res.streaming(out_stream))) + } + None => { + let (addr, out_stream) = + WebsocketContext::create_with_addr(self.actor, self.stream); + Ok((addr, res.streaming(out_stream))) + } + } + } +} /// Perform WebSocket handshake and start actor. +/// +/// To customize options, see [`WsResponseBuilder`]. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> + StreamHandler>, @@ -45,15 +228,15 @@ where /// Perform WebSocket handshake and start actor. /// -/// `req` is an HTTP Request that should be requesting a websocket protocol -/// change. `stream` should be a `Bytes` stream (such as -/// `actix_web::web::Payload`) that contains a stream of the body request. +/// `req` is an HTTP Request that should be requesting a websocket protocol change. `stream` should +/// be a `Bytes` stream (such as `actix_web::web::Payload`) that contains a stream of the +/// body request. /// /// If there is a problem with the handshake, an error is returned. /// -/// If successful, returns a pair where the first item is an address for the -/// created actor and the second item is the response that should be returned -/// from the WebSocket request. +/// If successful, returns a pair where the first item is an address for the created actor and the +/// second item is the response that should be returned from the WebSocket request. +#[deprecated(since = "4.0.0", note = "Prefer `WsResponseBuilder::start_with_addr`.")] pub fn start_with_addr( actor: A, req: &HttpRequest, @@ -71,6 +254,10 @@ where /// Do WebSocket handshake and start ws actor. /// /// `protocols` is a sequence of known protocols. +#[deprecated( + since = "4.0.0", + note = "Prefer `WsResponseBuilder` for setting protocols." +)] pub fn start_with_protocols( actor: A, protocols: &[&str], @@ -87,20 +274,19 @@ where /// Prepare WebSocket handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. +/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform +/// any IO. pub fn handshake(req: &HttpRequest) -> Result { handshake_with_protocols(req, &[]) } /// Prepare WebSocket handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. +/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform +/// any IO. /// -/// `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. +/// `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_with_protocols( req: &HttpRequest, protocols: &[&str], @@ -247,8 +433,8 @@ impl WebsocketContext where A: Actor, { + /// Create a new Websocket context from a request and an actor. #[inline] - /// Create a new Websocket context from a request and an actor pub fn create(actor: A, stream: S) -> impl Stream> where A: StreamHandler>, @@ -258,12 +444,11 @@ where stream } - #[inline] /// Create a new Websocket context from a request and an actor. /// - /// Returns a pair, where the first item is an addr for the created actor, - /// and the second item is a stream intended to be set as part of the - /// response via `HttpResponseBuilder::streaming()`. + /// Returns a pair, where the first item is an addr for the created actor, and the second item + /// is a stream intended to be set as part of the response + /// via [`HttpResponseBuilder::streaming()`]. pub fn create_with_addr( actor: A, stream: S, @@ -284,7 +469,6 @@ where (addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new())) } - #[inline] /// Create a new Websocket context from a request, an actor, and a codec pub fn with_codec( actor: A, @@ -300,7 +484,7 @@ where inner: ContextParts::new(mb.sender_producer()), messages: VecDeque::new(), }; - ctx.add_stream(WsStream::new(stream, codec)); + ctx.add_stream(WsStream::new(stream, codec.clone())); WebsocketContextFut::new(ctx, actor, mb, codec) } @@ -458,12 +642,13 @@ where M: ActixMessage + Send + 'static, M::Result: Send, { - fn pack(msg: M, tx: Option>) -> Envelope { + fn pack(msg: M, tx: Option>) -> Envelope { Envelope::new(msg, tx) } } pin_project! { + #[derive(Debug)] struct WsStream { #[pin] stream: S, @@ -549,9 +734,12 @@ where #[cfg(test)] mod tests { + use actix_web::{ + http::{header, Method}, + test::TestRequest, + }; + use super::*; - use actix_web::http::{header, Method}; - use actix_web::test::TestRequest; #[test] fn test_handshake() { diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index e481b2839..a9eb37699 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,11 +1,9 @@ use actix::prelude::*; -use actix_web::{ - http::{header, StatusCode}, - web, App, HttpRequest, HttpResponse, -}; -use actix_web_actors::*; +use actix_http::ws::Codec; +use actix_web::{web, App, HttpRequest}; +use actix_web_actors::ws; use bytes::Bytes; -use futures_util::{SinkExt as _, StreamExt as _}; +use futures_util::{SinkExt, StreamExt}; struct Ws; @@ -15,37 +13,34 @@ impl Actor for Ws { impl StreamHandler> for Ws { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg.unwrap() { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => {} + match msg { + Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), + Ok(ws::Message::Text(text)) => ctx.text(text), + Ok(ws::Message::Binary(bin)) => ctx.binary(bin), + Ok(ws::Message::Close(reason)) => ctx.close(reason), + _ => ctx.close(Some(ws::CloseCode::Error.into())), } } } -#[actix_rt::test] -async fn test_simple() { - let mut srv = actix_test::start(|| { - App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, - )) - }); +const MAX_FRAME_SIZE: usize = 10_000; +const DEFAULT_FRAME_SIZE: usize = 10; +async fn common_test_code(mut srv: actix_test::TestServer, frame_size: usize) { // client service let mut framed = srv.ws().await.unwrap(); - framed.send(ws::Message::Text("text".into())).await.unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); + let bytes = Bytes::from(vec![0; frame_size]); framed - .send(ws::Message::Binary("text".into())) + .send(ws::Message::Binary(bytes.clone())) .await .unwrap(); let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text"))); + assert_eq!(item, ws::Frame::Binary(bytes)); framed.send(ws::Message::Ping("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); @@ -55,55 +50,137 @@ async fn test_simple() { .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) .await .unwrap(); - let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); } #[actix_rt::test] -async fn test_with_credentials() { - let mut srv = actix_test::start(|| { +async fn simple_builder() { + let srv = actix_test::start(|| { App::new().service(web::resource("/").to( |req: HttpRequest, stream: web::Payload| async move { - if req.headers().contains_key("Authorization") { - ws::start(Ws, &req, stream) - } else { - Ok(HttpResponse::new(StatusCode::UNAUTHORIZED)) - } + ws::WsResponseBuilder::new(Ws, &req, stream).start() }, )) }); - // client service without credentials - match srv.ws().await { - Ok(_) => panic!("WebSocket client without credentials should panic"), - Err(awc::error::WsClientError::InvalidResponseStatus(status)) => { - assert_eq!(status, StatusCode::UNAUTHORIZED); - } - Err(e) => panic!("Invalid error from WebSocket client: {}", e), - } - - let headers = srv.client_headers().unwrap(); - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("Bearer Something"), - ); - - // client service with credentials - let client = srv.ws(); - - let mut framed = client.await.unwrap(); - - framed.send(ws::Message::Text("text".into())).await.unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_frame_size() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + common_test_code(srv, MAX_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_frame_size_exceeded() { + const MAX_FRAME_SIZE: usize = 64; + + let mut srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + // client service + let mut framed = srv.ws().await.unwrap(); + + // create a request with a frame size larger than expected + let bytes = Bytes::from(vec![0; MAX_FRAME_SIZE + 1]); + framed.send(ws::Message::Binary(bytes)).await.unwrap(); + + let frame = framed.next().await.unwrap().unwrap(); + let close_reason = match frame { + ws::Frame::Close(Some(reason)) => reason, + _ => panic!("close frame expected"), + }; + assert_eq!(close_reason.code, ws::CloseCode::Error); +} + +#[actix_rt::test] +async fn builder_with_codec() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .codec(Codec::new()) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_protocols() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .protocols(&["A", "B"]) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_with_codec_and_frame_size() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .codec(Codec::new()) + .frame_size(MAX_FRAME_SIZE) + .start() + }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn builder_full() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { + ws::WsResponseBuilder::new(Ws, &req, stream) + .frame_size(MAX_FRAME_SIZE) + .codec(Codec::new()) + .protocols(&["A", "B"]) + .start() + }, + )) + }); + + common_test_code(srv, MAX_FRAME_SIZE).await; +} + +#[actix_rt::test] +async fn simple_start() { + let srv = actix_test::start(|| { + App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, + )) + }); + + common_test_code(srv, DEFAULT_FRAME_SIZE).await; } From d89c706cd6653b9ce14ecbed4237435209e85f7b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 00:02:25 +0000 Subject: [PATCH 140/861] re-instate Range typed header (#2485) Co-authored-by: RideWindX --- CHANGES.md | 2 + src/http/header/mod.rs | 6 +- src/http/header/range.rs | 319 +++++++++++++++++++++------------------ 3 files changed, 179 insertions(+), 148 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b5a71001..78aa729df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * `AcceptEncoding` typed header. [#2482] +* `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] @@ -21,6 +22,7 @@ [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 +[#2485]: https://github.com/actix/actix-web/pull/2485 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 98548dadd..07b7592d7 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -40,7 +40,7 @@ mod if_unmodified_since; mod last_modified; mod macros; mod preference; -// mod range; +mod range; #[cfg(test)] pub(crate) use macros::common_header_test; @@ -68,7 +68,7 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; pub use self::preference::Preference; -//pub use self::range::{Range, ByteRangeSpec}; +pub use self::range::{ByteRangeSpec, Range}; /// Format writer ([`fmt::Write`]) for a [`BytesMut`]. #[derive(Debug, Default)] @@ -77,10 +77,12 @@ struct Writer { } impl Writer { + /// Constructs new bytes writer. pub fn new() -> Writer { Writer::default() } + /// Splits bytes out of writer, leaving writer buffer empty. pub fn take(&mut self) -> Bytes { self.buf.split().freeze() } diff --git a/src/http/header/range.rs b/src/http/header/range.rs index 11006ffff..c1d60f1ee 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -1,11 +1,12 @@ -// TODO: reinstate module - use std::{ - fmt::{self, Display}, + cmp, + fmt::{self, Display, Write}, str::FromStr, }; -use super::{parsing::from_one_raw_str, Header, Raw}; +use actix_http::{error::ParseError, header, HttpMessage}; + +use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// `Range` header, defined /// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) @@ -16,7 +17,7 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # ABNF /// ```plain -/// Range = byte-ranges-specifier / other-ranges-specifier +/// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set /// other-range-set = 1*VCHAR /// @@ -25,13 +26,15 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// byte-ranges-specifier = bytes-unit "=" byte-range-set /// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) /// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// suffix-byte-range-spec = "-" suffix-length +/// suffix-length = 1*DIGIT /// first-byte-pos = 1*DIGIT /// last-byte-pos = 1*DIGIT /// ``` /// /// # Example Values /// * `bytes=1000-` -/// * `bytes=-2000` +/// * `bytes=-50` /// * `bytes=0-1,30-40` /// * `bytes=0-10,20-90,-100` /// * `custom_unit=0-123` @@ -39,81 +42,81 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # Examples /// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; +/// use actix_web::http::header::{Range, ByteRangeSpec}; +/// use actix_web::HttpResponse; /// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] +/// let mut builder = HttpResponse::Ok(); +/// builder.insert_header(Range::Bytes( +/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::From(200)] /// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); +/// builder.insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); +/// builder.insert_header(Range::bytes(1, 100)); +/// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)])); /// ``` #[derive(PartialEq, Clone, Debug)] pub enum Range { - /// Byte range + /// Byte range. Bytes(Vec), - /// Custom range, with unit not registered at IANA + /// Custom range, with unit not registered at IANA. + /// /// (`other-range-unit`: String , `other-range-set`: String) Unregistered(String, String), } -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] +/// A range of bytes to fetch. +/// +/// Each [`Range::Bytes`] header can contain one or more `ByteRangeSpec`s. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") + /// All bytes from `x` to `y`, inclusive. + /// + /// Serialized as `x-y`. + /// + /// Example: `bytes=500-999` would represent the second 500 bytes. FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), + /// All bytes starting from `x`, inclusive. + /// + /// Serialized as `x-`. + /// + /// Example: For a file of 1000 bytes, `bytes=950-` would represent bytes 950-999, inclusive. + From(u64), - /// Get last x bytes ("-x") + /// The last `y` bytes, inclusive. + /// + /// Using the spec terminology, this is `suffix-byte-range-spec`. Serialized as `-y`. + /// + /// Example: For a file of 1000 bytes, `bytes=-50` is equivalent to `bytes=950-`. Last(u64), } impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. + /// Given the full length of the entity, attempt to normalize the byte range into an satisfiable + /// end-inclusive `(from, to)` range. /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. + /// The resulting range is guaranteed to be a satisfiable range within the bounds + /// of `0 <= from <= to < full_length`. /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. + /// If the byte range is deemed unsatisfiable, `None` is returned. An unsatisfiable range is + /// generally cause for a server to either reject the client request with a + /// `416 Range Not Satisfiable` status code, or to simply ignore the range header and serve the + /// full entity using a `200 OK` status code. /// - /// This function closely follows [RFC 7233 §2.1]. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: + /// This function closely follows [RFC 7233 §2.1]. As such, it considers ranges to be + /// satisfiable if they meet the following conditions: /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. + /// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that + /// is less than the current length of the representation, or at least one + /// suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set + /// is satisfiable. Otherwise, the byte-range-set is unsatisfiable. /// /// The function also computes remainder ranges based on the RFC: /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). + /// > If the last-byte-pos value is absent, or if the value is greater than or equal to the + /// current length of the representation data, the byte range is interpreted as the remainder + /// of the representation (i.e., the server replaces the value of last-byte-pos with a value + /// that is one less than the current length of the selected representation). /// /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233 pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { @@ -121,26 +124,28 @@ impl ByteRangeSpec { if full_length == 0 { return None; } - match self { - &ByteRangeSpec::FromTo(from, to) => { + + match *self { + ByteRangeSpec::FromTo(from, to) => { if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) + Some((from, cmp::min(to, full_length - 1))) } else { None } } - &ByteRangeSpec::AllFrom(from) => { + + ByteRangeSpec::From(from) => { if from < full_length { Some((from, full_length - 1)) } else { None } } - &ByteRangeSpec::Last(last) => { + + ByteRangeSpec::Last(last) => { if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. + // From the RFC: If the selected representation is shorter than the specified + // suffix-length, the entire representation is used. if last > full_length { Some((0, full_length - 1)) } else { @@ -155,48 +160,53 @@ impl ByteRangeSpec { } impl Range { - /// Get the most common byte range header ("bytes=from-to") + /// Constructs a common byte range header. + /// + /// Eg: `bytes=from-to` pub fn bytes(from: u64, to: u64) -> Range { Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) } - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") + /// Constructs a byte range header with multiple subranges. + /// + /// Eg: `bytes=from1-to1,from2-to2,fromX-toX` pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { Range::Bytes( ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .into_iter() + .map(|(from, to)| ByteRangeSpec::FromTo(from, to)) .collect(), ) } } impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + ByteRangeSpec::From(pos) => write!(f, "{}-", pos), } } } impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Range::Bytes(ranges) => { write!(f, "bytes=")?; for (i, range) in ranges.iter().enumerate() { if i != 0 { f.write_str(",")?; } + Display::fmt(range, f)?; } Ok(()) } - Range::Unregistered(ref unit, ref range_str) => { + + Range::Unregistered(unit, range_str) => { write!(f, "{}={}", unit, range_str) } } @@ -204,89 +214,118 @@ impl fmt::Display for Range { } impl FromStr for Range { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); + fn from_str(s: &str) -> Result { + let (unit, val) = s.split_once('=').ok_or(ParseError::Header)?; - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { + match (unit, val) { + ("bytes", ranges) => { let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { - return Err(::Error::Header); + return Err(ParseError::Header); } + Ok(Range::Bytes(ranges)) } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { - Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) - } - _ => Err(::Error::Header), + + (_, "") => Err(ParseError::Header), + ("", _) => Err(ParseError::Header), + + (unit, range_str) => Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())), } } } impl FromStr for ByteRangeSpec { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); + fn from_str(s: &str) -> Result { + let (start, end) = s.split_once('-').ok_or(ParseError::Header)?; - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end + match (start, end) { + ("", end) => end .parse() - .or(Err(::Error::Header)) + .or(Err(ParseError::Header)) .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start + + (start, "") => start .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { + .or(Err(ParseError::Header)) + .map(ByteRangeSpec::From), + + (start, end) => match (start.parse(), end.parse()) { (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), - _ => Err(::Error::Header), + _ => Err(ParseError::Header), }, - _ => Err(::Error::Header), } } } impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME + fn name() -> HeaderName { + header::RANGE } - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) + #[inline] + fn parse(msg: &T) -> Result { + header::from_one_raw_str(msg.headers().get(&Self::name())) } +} - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) +impl IntoHeaderValue for Range { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + let mut wrt = Writer::new(); + let _ = write!(wrt, "{}", self); + HeaderValue::from_maybe_shared(wrt.take()) } } +/// Parses 0 or more items out of a comma delimited string, ignoring invalid items. +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + #[cfg(test)] mod tests { + use actix_http::{test::TestRequest, Request}; + use super::*; + fn req(s: &str) -> Request { + TestRequest::default() + .insert_header((header::RANGE, s)) + .finish() + } + #[test] fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100,-")).unwrap(); let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,200-")).unwrap(); + let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx, 200- ")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), + ByteRangeSpec::From(200), ]); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100, ,,-100")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100), @@ -294,71 +333,65 @@ mod tests { assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r: Range = Header::parse(&req("custom=abcd")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r: Range = Header::parse(&req("custom=xxx-yyy")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); + let r: Result = Header::parse(&req("bytes=1-a,-")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); + let r: Result = Header::parse(&req("bytes=1-2-3")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"abc".into()); + let r: Result = Header::parse(&req("abc")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); + let r: Result = Header::parse(&req("bytes=1-100=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=".into()); + let r: Result = Header::parse(&req("bytes=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"custom=".into()); + let r: Result = Header::parse(&req("custom=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"=1-100".into()); + let r: Result = Header::parse(&req("=1-100")); assert_eq!(r.ok(), None); } #[test] fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ + let range = Range::Bytes(vec![ ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + ByteRangeSpec::From(2000), + ]); + assert_eq!(&range.to_string(), "bytes=0-1000,2000-"); - headers.clear(); - headers.set(Range::Bytes(vec![])); + let range = Range::Bytes(vec![]); - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + assert_eq!(&range.to_string(), "bytes="); - headers.clear(); - headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); + let range = Range::Unregistered("custom".to_owned(), "1-xxx".to_owned()); - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); + assert_eq!(&range.to_string(), "custom=1-xxx"); } #[test] @@ -379,17 +412,11 @@ mod tests { assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); + assert_eq!(Some((0, 2)), ByteRangeSpec::From(0).to_satisfiable_range(3)); + assert_eq!(Some((2, 2)), ByteRangeSpec::From(2).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(3).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(0).to_satisfiable_range(0)); assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); From e1a2d9c60684820723a539f4005f3af35281884a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 03:38:08 +0000 Subject: [PATCH 141/861] `Quality` / `QualityItem` improvements (#2486) --- .cargo/config.toml | 2 + actix-http/CHANGES.md | 6 + actix-http/Cargo.toml | 4 + actix-http/benches/quality-value.rs | 90 ++++++++ actix-http/src/header/mod.rs | 7 +- actix-http/src/header/shared/extended.rs | 2 +- actix-http/src/header/shared/mod.rs | 4 +- actix-http/src/header/shared/quality.rs | 208 ++++++++++++++++++ actix-http/src/header/shared/quality_item.rs | 216 ++++++------------- actix-http/src/header/utils.rs | 5 +- actix-multipart/src/server.rs | 11 +- src/http/header/accept.rs | 74 +++---- src/http/header/accept_charset.rs | 12 +- src/http/header/accept_encoding.rs | 24 ++- src/http/header/accept_language.rs | 76 ++++--- src/http/header/content_language.rs | 10 +- 16 files changed, 494 insertions(+), 257 deletions(-) create mode 100644 actix-http/benches/quality-value.rs create mode 100644 actix-http/src/header/shared/quality.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 606c30de7..4425e0dda 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,8 +5,10 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli # lib checking ci-check-min = "hack --workspace check --no-default-features" ci-check-default = "hack --workspace check" +ci-check-default-tests = "check --workspace --tests" ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check" ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check" # testing +ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 773f1ff39..877380581 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -11,6 +11,9 @@ * `impl Clone for ws::HandshakeError`. [#2468] * `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] * `impl Default ` for `ws::Codec`. [#1920] +* `header::QualityItem::{max, min}`. [#2486] +* `header::Quality::{MAX, MIN}`. [#2486] +* `impl Display` for `header::Quality`. [#2486] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -27,10 +30,13 @@ * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * `impl Copy` for `ws::Codec`. [#1920] +* `header::qitem` helper. Replaced with `header::QualityItem::max` [#2486] +* `impl TryFrom` for `header::Quality` [#2486] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 [#1920]: https://github.com/actix/actix-web/pull/1920 +[#2486]: https://github.com/actix/actix-web/pull/2486 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f8b15df75..967f04d03 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -112,3 +112,7 @@ harness = false [[bench]] name = "uninit-headers" harness = false + +[[bench]] +name = "quality-value" +harness = false diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs new file mode 100644 index 000000000..31b67f999 --- /dev/null +++ b/actix-http/benches/quality-value.rs @@ -0,0 +1,90 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +const CODES: &[u16] = &[0, 1000, 201, 800, 550]; + +fn bench_quality_display_impls(c: &mut Criterion) { + let mut group = c.benchmark_group("quality value display impls"); + + for i in CODES.iter() { + group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| { + b.iter(|| _new::Quality(i).to_string()) + }); + + group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { + b.iter(|| _naive::Quality(i).to_string()) + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_quality_display_impls); +criterion_main!(benches); + +mod _new { + use std::fmt; + + pub struct Quality(pub(crate) u16); + + impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + 1000 => f.write_str("1"), + + // some number in the range 1–999 + x => { + f.write_str("0.")?; + + // this implementation avoids string allocation otherwise required + // for `.trim_end_matches('0')` + + if x < 10 { + f.write_str("00")?; + // 0 is handled so it's not possible to have a trailing 0, we can just return + itoa::fmt(f, x) + } else if x < 100 { + f.write_str("0")?; + if x % 10 == 0 { + // trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } else { + // x is in range 101–999 + + if x % 100 == 0 { + // two trailing 0s, divide by 100 and write + itoa::fmt(f, x / 100) + } else if x % 10 == 0 { + // one trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } + } + } + } + } +} + +mod _naive { + use std::fmt; + + pub struct Quality(pub(crate) u16); + + impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + 1000 => f.write_str("1"), + + x => { + write!(f, "{}", format!("{:03}", x).trim_end_matches('0')) + } + } + } + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 308cb0123..381842e74 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -38,13 +38,14 @@ pub mod map; mod shared; mod utils; -#[doc(hidden)] -pub use self::shared::*; - pub use self::as_name::AsHeaderName; pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; pub use self::map::HeaderMap; +pub use self::shared::{ + parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, + LanguageTag, Quality, QualityItem, +}; pub use self::utils::{ fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, }; diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index b2cf1d754..60f2d359e 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -1,4 +1,4 @@ -// Originally from hyper v0.11.27 src/header/parsing.rs +//! Originally taken from `hyper::header::parsing`. use std::{fmt, str::FromStr}; diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index 274e13146..257e54d7a 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -4,11 +4,13 @@ mod charset; mod content_encoding; mod extended; mod http_date; +mod quality; mod quality_item; pub use self::charset::Charset; pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::http_date::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use self::quality::{q, Quality}; +pub use self::quality_item::QualityItem; pub use language_tags::LanguageTag; diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs new file mode 100644 index 000000000..5321c754d --- /dev/null +++ b/actix-http/src/header/shared/quality.rs @@ -0,0 +1,208 @@ +use std::{ + convert::{TryFrom, TryInto}, + fmt, +}; + +use derive_more::{Display, Error}; + +const MAX_QUALITY_INT: u16 = 1000; +const MAX_QUALITY_FLOAT: f32 = 1.0; + +/// Represents a quality used in q-factor values. +/// +/// The default value is equivalent to `q=1.0` (the [max](Self::MAX) value). +/// +/// # Implementation notes +/// The quality value is defined as a number between 0.0 and 1.0 with three decimal places. +/// This means there are 1001 possible values. Since floating point numbers are not exact and the +/// smallest floating point data type (`f32`) consumes four bytes, we use an `u16` value to store +/// the quality internally. +/// +/// [RFC 7231 §5.3.1] gives more information on quality values in HTTP header fields. +/// +/// # Examples +/// ``` +/// use actix_http::header::{Quality, q}; +/// assert_eq!(q(1.0), Quality::MAX); +/// +/// assert_eq!(q(0.42).to_string(), "0.42"); +/// assert_eq!(q(1.0).to_string(), "1"); +/// assert_eq!(Quality::MIN.to_string(), "0"); +/// ``` +/// +/// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1 +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Quality(pub(super) u16); + +impl Quality { + /// The maximum quality value, equivalent to `q=1.0`. + pub const MAX: Quality = Quality(MAX_QUALITY_INT); + + /// The minimum quality value, equivalent to `q=0.0`. + pub const MIN: Quality = Quality(0); + + /// Converts a float in the range 0.0–1.0 to a `Quality`. + /// + /// Intentionally private. External uses should rely on the `TryFrom` impl. + /// + /// # Panics + /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. + fn from_f32(value: f32) -> Self { + // Check that `value` is within range should be done before calling this method. + // Just in case, this debug_assert should catch if we were forgetful. + debug_assert!( + (0.0f32..=1.0f32).contains(&value), + "q value must be between 0.0 and 1.0" + ); + + Quality((value * MAX_QUALITY_INT as f32) as u16) + } +} + +/// The default value is [`Quality::MAX`]. +impl Default for Quality { + fn default() -> Quality { + Quality::MAX + } +} + +impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + 0 => f.write_str("0"), + MAX_QUALITY_INT => f.write_str("1"), + + // some number in the range 1–999 + x => { + f.write_str("0.")?; + + // This implementation avoids string allocation for removing trailing zeroes. + // In benchmarks it is twice as fast as approach using something like + // `format!("{}").trim_end_matches('0')` for non-fast-path quality values. + + if x < 10 { + // x in is range 1–9 + + f.write_str("00")?; + + // 0 is already handled so it's not possible to have a trailing 0 in this range + // we can just write the integer + itoa::fmt(f, x) + } else if x < 100 { + // x in is range 10–99 + + f.write_str("0")?; + + if x % 10 == 0 { + // trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } else { + // x is in range 100–999 + + if x % 100 == 0 { + // two trailing 0s, divide by 100 and write + itoa::fmt(f, x / 100) + } else if x % 10 == 0 { + // one trailing 0, divide by 10 and write + itoa::fmt(f, x / 10) + } else { + itoa::fmt(f, x) + } + } + } + } + } +} + +#[derive(Debug, Clone, Display, Error)] +#[display(fmt = "quality out of bounds")] +#[non_exhaustive] +pub struct QualityOutOfBounds; + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + #[inline] + fn try_from(value: f32) -> Result { + if (0.0..=MAX_QUALITY_FLOAT).contains(&value) { + Ok(Quality::from_f32(value)) + } else { + Err(QualityOutOfBounds) + } + } +} + +/// Convenience function to create a [`Quality`] from an `f32` (0.0–1.0). +/// +/// Not recommended for use with user input. Rely on the `TryFrom` impls where possible. +/// +/// # Panics +/// Panics if value is out of range. +/// +/// # Examples +/// ``` +/// # use actix_http::header::{q, Quality}; +/// let q1 = q(1.0); +/// assert_eq!(q1, Quality::MAX); +/// +/// let q2 = q(0.0); +/// assert_eq!(q2, Quality::MIN); +/// +/// let q3 = q(0.42); +/// ``` +/// +/// An out-of-range `f32` quality will panic. +/// ```should_panic +/// # use actix_http::header::q; +/// let _q2 = q(1.42); +/// ``` +#[inline] +pub fn q(quality: T) -> Quality +where + T: TryInto, + T::Error: fmt::Debug, +{ + quality.try_into().expect("quality value was out of bounds") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn q_helper() { + assert_eq!(q(0.5), Quality(500)); + } + + #[test] + fn display_output() { + assert_eq!(q(0.0).to_string(), "0"); + assert_eq!(q(1.0).to_string(), "1"); + assert_eq!(q(0.001).to_string(), "0.001"); + assert_eq!(q(0.5).to_string(), "0.5"); + assert_eq!(q(0.22).to_string(), "0.22"); + assert_eq!(q(0.123).to_string(), "0.123"); + assert_eq!(q(0.999).to_string(), "0.999"); + + for x in 0..=1000 { + // if trailing zeroes are handled correctly, we would not expect the serialized length + // to ever exceed "0." + 3 decimal places = 5 in length + assert!(q(x as f32 / 1000.0).to_string().len() <= 5); + } + } + + #[test] + #[should_panic] + fn negative_quality() { + q(-1.0); + } + + #[test] + #[should_panic] + fn quality_out_of_bounds() { + q(2.0); + } +} diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index a109b44ea..9354915ad 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,85 +1,36 @@ -use std::{ - cmp, - convert::{TryFrom, TryInto}, - fmt, str, -}; - -use derive_more::{Display, Error}; +use std::{cmp, convert::TryFrom as _, fmt, str}; use crate::error::ParseError; -const MAX_QUALITY: u16 = 1000; -const MAX_FLOAT_QUALITY: f32 = 1.0; - -/// Represents a quality used in quality values. -/// -/// Can be created with the [`q`] function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more -/// information on quality values in HTTP header fields. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Quality(u16); - -impl Quality { - /// # Panics - /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. - fn from_f32(value: f32) -> Self { - // Check that `value` is within range should be done before calling this method. - // Just in case, this debug_assert should catch if we were forgetful. - debug_assert!( - (0.0f32..=1.0f32).contains(&value), - "q value must be between 0.0 and 1.0" - ); - - Quality((value * MAX_QUALITY as f32) as u16) - } -} - -impl Default for Quality { - fn default() -> Quality { - Quality(MAX_QUALITY) - } -} - -#[derive(Debug, Clone, Display, Error)] -pub struct QualityOutOfBounds; - -impl TryFrom for Quality { - type Error = QualityOutOfBounds; - - fn try_from(value: u16) -> Result { - if (0..=MAX_QUALITY).contains(&value) { - Ok(Quality(value)) - } else { - Err(QualityOutOfBounds) - } - } -} - -impl TryFrom for Quality { - type Error = QualityOutOfBounds; - - fn try_from(value: f32) -> Result { - if (0.0..=MAX_FLOAT_QUALITY).contains(&value) { - Ok(Quality::from_f32(value)) - } else { - Err(QualityOutOfBounds) - } - } -} +use super::Quality; /// Represents an item with a quality value as defined /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). +/// +/// # Parsing and Formatting +/// This wrapper be used to parse header value items that have a q-factor annotation as well as +/// serialize items with a their q-factor. +/// +/// # Ordering +/// Since this context of use for this type is header value items, ordering is defined for +/// `QualityItem`s but _only_ considers the item's quality. Order of appearance should be used as +/// the secondary sorting parameter; i.e., a stable sort over the quality values will produce a +/// correctly sorted sequence. +/// +/// # Examples +/// ``` +/// # use actix_http::header::{QualityItem, q}; +/// let q_item: QualityItem = "hello;q=0.3".parse().unwrap(); +/// assert_eq!(&q_item.item, "hello"); +/// assert_eq!(q_item.quality, q(0.3)); +/// +/// // note that format is normalized compared to parsed item +/// assert_eq!(q_item.to_string(), "hello; q=0.3"); +/// +/// // item with q=0.3 is greater than item with q=0.1 +/// let q_item_fallback: QualityItem = "abc;q=0.1".parse().unwrap(); +/// assert!(q_item > q_item_fallback); +/// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct QualityItem { /// The wrapped contents of the field. @@ -93,12 +44,22 @@ impl QualityItem { /// Constructs a new `QualityItem` from an item and a quality value. /// /// The item can be of any type. The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { + pub fn new(item: T, quality: Quality) -> Self { QualityItem { item, quality } } + + /// Constructs a new `QualityItem` from an item, using the maximum q-value. + pub fn max(item: T) -> Self { + Self::new(item, Quality::MAX) + } + + /// Constructs a new `QualityItem` from an item, using the minimum q-value. + pub fn min(item: T) -> Self { + Self::new(item, Quality::MIN) + } } -impl cmp::PartialOrd for QualityItem { +impl PartialOrd for QualityItem { fn partial_cmp(&self, other: &QualityItem) -> Option { self.quality.partial_cmp(&other.quality) } @@ -108,10 +69,12 @@ impl fmt::Display for QualityItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - MAX_QUALITY => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), + match self.quality { + // q-factor value is implied for max value + Quality::MAX => Ok(()), + + Quality::MIN => f.write_str("; q=0"), + q => write!(f, "; q={}", q), } } } @@ -119,78 +82,58 @@ impl fmt::Display for QualityItem { impl str::FromStr for QualityItem { type Err = ParseError; - fn from_str(qitem_str: &str) -> Result { - if !qitem_str.is_ascii() { + fn from_str(q_item_str: &str) -> Result { + if !q_item_str.is_ascii() { return Err(ParseError::Header); } - // Set defaults used if parsing fails. - let mut raw_item = qitem_str; - let mut quality = 1f32; + // set defaults used if quality-item parsing fails, i.e., item has no q attribute + let mut raw_item = q_item_str; + let mut quality = Quality::MAX; - // TODO: MSRV(1.52): use rsplit_once - let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); + let parts = q_item_str + .rsplit_once(';') + .map(|(item, q_attr)| (item.trim(), q_attr.trim())); - if parts.len() == 2 { + if let Some((val, q_attr)) = parts { // example for item with q-factor: // - // gzip; q=0.65 - // ^^^^^^ parts[0] - // ^^ start - // ^^^^ q_val - // ^^^^ parts[1] + // gzip;q=0.65 + // ^^^^ val + // ^^^^^^ q_attr + // ^^ q + // ^^^^ q_val - if parts[0].len() < 2 { + if q_attr.len() < 2 { // Can't possibly be an attribute since an attribute needs at least a name followed // by an equals sign. And bare identifiers are forbidden. return Err(ParseError::Header); } - let start = &parts[0][0..2]; + let q = &q_attr[0..2]; - if start == "q=" || start == "Q=" { - let q_val = &parts[0][2..]; + if q == "q=" || q == "Q=" { + let q_val = &q_attr[2..]; if q_val.len() > 5 { // longer than 5 indicates an over-precise q-factor return Err(ParseError::Header); } let q_value = q_val.parse::().map_err(|_| ParseError::Header)?; + let q_value = + Quality::try_from(q_value).map_err(|_| ParseError::Header)?; - if (0f32..=1f32).contains(&q_value) { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(ParseError::Header); - } + quality = q_value; + raw_item = val; } } let item = raw_item.parse::().map_err(|_| ParseError::Header)?; - // we already checked above that the quality is within range - Ok(QualityItem::new(item, Quality::from_f32(quality))) + Ok(QualityItem::new(item, quality)) } } -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Quality::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality -where - T: TryInto, - T::Error: fmt::Debug, -{ - // TODO: on next breaking change, handle unwrap differently - val.try_into().unwrap() -} - #[cfg(test)] mod tests { use super::*; @@ -245,7 +188,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_1() { use Encoding::*; - let x = qitem(Chunked); + let x = QualityItem::max(Chunked); assert_eq!(format!("{}", x), "chunked"); } #[test] @@ -344,25 +287,8 @@ mod tests { fn test_quality_item_ordering() { let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] - fn test_quality_invalid2() { - q(2.0); + let comparison_result: bool = x.gt(&y); + assert!(comparison_result) } #[test] diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index a23f5b751..f4f34d347 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -65,8 +65,9 @@ where Ok(()) } -/// Percent encode a sequence of bytes with a character set defined in -/// +/// Percent encode a sequence of bytes with a character set defined in [RFC 5987 §3.2]. +/// +/// [RFC 5987 §3.2]: https://datatracker.ietf.org/doc/html/rfc5987#section-3.2 #[inline] pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 23397b7ee..8eabcee10 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -435,10 +435,10 @@ impl Field { /// Returns the field's Content-Disposition. /// - /// Per [RFC 7578 §4.2]: 'Each part MUST contain a Content-Disposition header field where the - /// disposition type is "form-data". The Content-Disposition header field MUST also contain an - /// additional parameter of "name"; the value of the "name" parameter is the original field name - /// from the form.' + /// Per [RFC 7578 §4.2]: "Each part MUST contain a Content-Disposition header field where the + /// disposition type is `form-data`. The Content-Disposition header field MUST also contain an + /// additional parameter of `name`; the value of the `name` parameter is the original field name + /// from the form." /// /// This crate validates that it exists before returning a `Field`. As such, it is safe to /// unwrap `.content_disposition().get_name()`. The [name](Self::name) method is provided as @@ -451,7 +451,8 @@ impl Field { /// Returns the field's name. /// - /// See [content_disposition] regarding guarantees about + /// See [content_disposition](Self::content_disposition) regarding guarantees about existence of + /// the name field. pub fn name(&self) -> &str { self.content_disposition() .get_name() diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 70e4118cf..c61e6ab49 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use mime::Mime; -use super::{qitem, QualityItem}; +use super::QualityItem; use crate::http::header; crate::http::header::common_header! { @@ -34,46 +34,40 @@ crate::http::header::common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_web::http::header::{Accept, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::TEXT_HTML), + /// QualityItem::max(mime::TEXT_HTML), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_web::http::header::{Accept, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), + /// QualityItem::max(mime::APPLICATION_JSON), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_web::http::header::{Accept, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), + /// QualityItem::max(mime::TEXT_HTML), + /// QualityItem::max("application/xhtml+xml".parse().unwrap()), + /// QualityItem::new(mime::TEXT_XML, q(0.9)), + /// QualityItem::max("image/webp".parse().unwrap()), + /// QualityItem::new(mime::STAR_STAR, q(0.8)), /// ]) /// ); /// ``` @@ -85,20 +79,20 @@ crate::http::header::common_header! { test1, vec![b"audio/*; q=0.2, audio/basic"], Some(Accept(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), + QualityItem::new("audio/*".parse().unwrap(), q(0.2)), + QualityItem::max("audio/basic".parse().unwrap()), ]))); crate::http::header::common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(Accept(vec![ - QualityItem::new(mime::TEXT_PLAIN, q(500)), - qitem(mime::TEXT_HTML), + QualityItem::new(mime::TEXT_PLAIN, q(0.5)), + QualityItem::max(mime::TEXT_HTML), QualityItem::new( "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), + q(0.8)), + QualityItem::max("text/x-c".parse().unwrap()), ]))); // Custom tests @@ -106,14 +100,14 @@ crate::http::header::common_header! { test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ - qitem(mime::TEXT_PLAIN_UTF_8), + QualityItem::max(mime::TEXT_PLAIN_UTF_8), ]))); crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ QualityItem::new(mime::TEXT_PLAIN_UTF_8, - q(500)), + q(0.5)), ]))); #[test] @@ -130,27 +124,27 @@ crate::http::header::common_header! { impl Accept { /// Construct `Accept: */*`. pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) + Accept(vec![QualityItem::max(mime::STAR_STAR)]) } /// Construct `Accept: application/json`. pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) + Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]) } /// Construct `Accept: text/*`. pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) + Accept(vec![QualityItem::max(mime::TEXT_STAR)]) } /// Construct `Accept: image/*`. pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) + Accept(vec![QualityItem::max(mime::IMAGE_STAR)]) } /// Construct `Accept: text/html`. pub fn html() -> Accept { - Accept(vec![qitem(mime::TEXT_HTML)]) + Accept(vec![QualityItem::max(mime::TEXT_HTML)]) } /// Returns a sorted list of mime types from highest to lowest preference, accounting for @@ -213,10 +207,10 @@ impl Accept { /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn preference(&self) -> Mime { - use actix_http::header::q; + use actix_http::header::Quality; let mut max_item = None; - let mut max_pref = q(0); + let mut max_pref = Quality::MIN; // uses manual max lookup loop since we want the first occurrence in the case of same // preference but `Iterator::max_by_key` would give us the last occurrence @@ -244,11 +238,11 @@ mod tests { let test = Accept(vec![]); assert!(test.ranked().is_empty()); - let test = Accept(vec![qitem(mime::APPLICATION_JSON)]); + let test = Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]); assert_eq!(test.ranked(), vec!(mime::APPLICATION_JSON)); let test = Accept(vec![ - qitem(mime::TEXT_HTML), + QualityItem::max(mime::TEXT_HTML), "application/xhtml+xml".parse().unwrap(), QualityItem::new("application/xml".parse().unwrap(), q(0.9)), QualityItem::new(mime::STAR_STAR, q(0.8)), @@ -264,9 +258,9 @@ mod tests { ); let test = Accept(vec![ - qitem(mime::STAR_STAR), - qitem(mime::IMAGE_STAR), - qitem(mime::IMAGE_PNG), + QualityItem::max(mime::STAR_STAR), + QualityItem::max(mime::IMAGE_STAR), + QualityItem::max(mime::IMAGE_PNG), ]); assert_eq!( test.ranked(), @@ -277,7 +271,7 @@ mod tests { #[test] fn preference_selection() { let test = Accept(vec![ - qitem(mime::TEXT_HTML), + QualityItem::max(mime::TEXT_HTML), "application/xhtml+xml".parse().unwrap(), QualityItem::new("application/xml".parse().unwrap(), q(0.9)), QualityItem::new(mime::STAR_STAR, q(0.8)), @@ -286,9 +280,9 @@ mod tests { let test = Accept(vec![ QualityItem::new("video/*".parse().unwrap(), q(0.8)), - qitem(mime::IMAGE_PNG), + QualityItem::max(mime::IMAGE_PNG), QualityItem::new(mime::STAR_STAR, q(0.5)), - qitem(mime::IMAGE_SVG), + QualityItem::max(mime::IMAGE_SVG), QualityItem::new(mime::IMAGE_STAR, q(0.8)), ]); assert_eq!(test.preference(), mime::IMAGE_PNG); diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index 5577ab604..c8b918c91 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -22,11 +22,11 @@ crate::http::header::common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) + /// AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)]) /// ); /// ``` /// @@ -37,19 +37,19 @@ crate::http::header::common_header! { /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), + /// QualityItem::new(Charset::Us_Ascii, q(0.9)), + /// QualityItem::new(Charset::Iso_8859_10, q(0.2)), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) + /// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))]) /// ); /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 85cd0a4f7..828a0533c 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -29,36 +29,38 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) + /// AcceptEncoding(vec![QualityItem::max(Encoding::Chunked)]) /// ); /// ``` + /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), + /// QualityItem::max(Encoding::Chunked), + /// QualityItem::max(Encoding::Gzip), + /// QualityItem::max(Encoding::Deflate), /// ]) /// ); /// ``` + /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), + /// QualityItem::max(Encoding::Chunked), + /// QualityItem::new(Encoding::Gzip, q(0.60)), + /// QualityItem::min(Encoding::EncodingExt("*".to_owned())), /// ]) /// ); /// ``` @@ -77,3 +79,5 @@ common_header! { common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } + +// TODO: shortcut for EncodingExt(*) = Any diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 229f95ef1..011257b87 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,6 +1,6 @@ use language_tags::LanguageTag; -use super::{common_header, Preference, QualityItem}; +use super::{common_header, Preference, Quality, QualityItem}; use crate::http::header; common_header! { @@ -32,26 +32,26 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, qitem}; + /// use actix_web::http::header::{AcceptLanguage, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem("en-US".parse().unwrap()) + /// QualityItem::max("en-US".parse().unwrap()) /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// qitem("da".parse().unwrap()), - /// QualityItem::new("en-GB".parse().unwrap(), q(800)), - /// QualityItem::new("en".parse().unwrap(), q(700)), + /// QualityItem::max("da".parse().unwrap()), + /// QualityItem::new("en-GB".parse().unwrap(), q(0.8)), + /// QualityItem::new("en".parse().unwrap(), q(0.7)), /// ]) /// ); /// ``` @@ -72,9 +72,9 @@ common_header! { not_ordered_by_weight, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), + QualityItem::max("en-US".parse().unwrap()), + QualityItem::new("en".parse().unwrap(), q(0.5)), + QualityItem::max("fr".parse().unwrap()), ])) ); @@ -82,11 +82,11 @@ common_header! { has_wildcard, vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"], Some(AcceptLanguage(vec![ - qitem("fr-CH".parse().unwrap()), - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("de".parse().unwrap(), q(700)), - QualityItem::new("*".parse().unwrap(), q(500)), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::new("fr".parse().unwrap(), q(0.9)), + QualityItem::new("en".parse().unwrap(), q(0.8)), + QualityItem::new("de".parse().unwrap(), q(0.7)), + QualityItem::new("*".parse().unwrap(), q(0.5)), ])) ); } @@ -122,10 +122,8 @@ impl AcceptLanguage { /// /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 pub fn preference(&self) -> Preference { - use actix_http::header::q; - let mut max_item = None; - let mut max_pref = q(0); + let mut max_pref = Quality::MIN; // uses manual max lookup loop since we want the first occurrence in the case of same // preference but `Iterator::max_by_key` would give us the last occurrence @@ -153,15 +151,15 @@ mod tests { let test = AcceptLanguage(vec![]); assert!(test.ranked().is_empty()); - let test = AcceptLanguage(vec![qitem("fr-CH".parse().unwrap())]); + let test = AcceptLanguage(vec![QualityItem::max("fr-CH".parse().unwrap())]); assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap())); let test = AcceptLanguage(vec![ - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("fr-CH".parse().unwrap(), q(1000)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("*".parse().unwrap(), q(500)), - QualityItem::new("de".parse().unwrap(), q(700)), + QualityItem::new("fr".parse().unwrap(), q(0.900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1.0)), + QualityItem::new("en".parse().unwrap(), q(0.800)), + QualityItem::new("*".parse().unwrap(), q(0.500)), + QualityItem::new("de".parse().unwrap(), q(0.700)), ]); assert_eq!( test.ranked(), @@ -175,11 +173,11 @@ mod tests { ); let test = AcceptLanguage(vec![ - qitem("fr".parse().unwrap()), - qitem("fr-CH".parse().unwrap()), - qitem("en".parse().unwrap()), - qitem("*".parse().unwrap()), - qitem("de".parse().unwrap()), + QualityItem::max("fr".parse().unwrap()), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::max("en".parse().unwrap()), + QualityItem::max("*".parse().unwrap()), + QualityItem::max("de".parse().unwrap()), ]); assert_eq!( test.ranked(), @@ -196,11 +194,11 @@ mod tests { #[test] fn preference_selection() { let test = AcceptLanguage(vec![ - QualityItem::new("fr".parse().unwrap(), q(900)), - QualityItem::new("fr-CH".parse().unwrap(), q(1000)), - QualityItem::new("en".parse().unwrap(), q(800)), - QualityItem::new("*".parse().unwrap(), q(500)), - QualityItem::new("de".parse().unwrap(), q(700)), + QualityItem::new("fr".parse().unwrap(), q(0.900)), + QualityItem::new("fr-CH".parse().unwrap(), q(1.0)), + QualityItem::new("en".parse().unwrap(), q(0.800)), + QualityItem::new("*".parse().unwrap(), q(0.500)), + QualityItem::new("de".parse().unwrap(), q(0.700)), ]); assert_eq!( test.preference(), @@ -208,11 +206,11 @@ mod tests { ); let test = AcceptLanguage(vec![ - qitem("fr".parse().unwrap()), - qitem("fr-CH".parse().unwrap()), - qitem("en".parse().unwrap()), - qitem("*".parse().unwrap()), - qitem("de".parse().unwrap()), + QualityItem::max("fr".parse().unwrap()), + QualityItem::max("fr-CH".parse().unwrap()), + QualityItem::max("en".parse().unwrap()), + QualityItem::max("*".parse().unwrap()), + QualityItem::max("de".parse().unwrap()), ]); assert_eq!( test.preference(), diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index 39ca8da56..ff317e1de 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -23,25 +23,25 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(LanguageTag::parse("en").unwrap()), + /// QualityItem::max(LanguageTag::parse("en").unwrap()), /// ]) /// ); /// ``` /// /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem}; + /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ - /// qitem(LanguageTag::parse("da").unwrap()), - /// qitem(LanguageTag::parse("en-GB").unwrap()), + /// QualityItem::max(LanguageTag::parse("da").unwrap()), + /// QualityItem::max(LanguageTag::parse("en-GB").unwrap()), /// ]) /// ); /// ``` From 59be0c65c6b4766355d8bf08de27531d03730bdd Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sun, 5 Dec 2021 07:39:18 +0300 Subject: [PATCH 142/861] disallow query or fragements in `url_for` constructions (#2430) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/error/mod.rs | 8 +++--- src/request.rs | 46 +++++++++++++++++------------ src/rmap.rs | 75 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 96 insertions(+), 35 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 78aa729df..1b108fee6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,12 +12,14 @@ * Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] +* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] * Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +[#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 diff --git a/src/error/mod.rs b/src/error/mod.rs index 3ccd5bba6..46d0dccc6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -29,15 +29,15 @@ pub type Result = std::result::Result; #[derive(Debug, PartialEq, Display, Error, From)] #[non_exhaustive] pub enum UrlGenerationError { - /// Resource not found + /// Resource not found. #[display(fmt = "Resource not found")] ResourceNotFound, - /// Not all path pattern covered - #[display(fmt = "Not all path pattern covered")] + /// Not all URL parameters covered. + #[display(fmt = "Not all URL parameters covered")] NotEnoughElements, - /// URL parse error + /// URL parse error. #[display(fmt = "{}", _0)] ParseError(UrlParseError), } diff --git a/src/request.rs b/src/request.rs index 0027f9b4b..58222da47 100644 --- a/src/request.rs +++ b/src/request.rs @@ -100,7 +100,7 @@ impl HttpRequest { &self.head().headers } - /// The target path of this Request. + /// The target path of this request. #[inline] pub fn path(&self) -> &str { self.head().uri.path() @@ -108,18 +108,22 @@ impl HttpRequest { /// The query string in the URL. /// - /// E.g., id=10 + /// Example: `id=10` #[inline] pub fn query_string(&self) -> &str { self.uri().query().unwrap_or_default() } - /// Get a reference to the Path parameters. + /// Returns a reference to the URL parameters container. /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. + /// A url parameter is specified in the form `{identifier}`, where the identifier can be used + /// later in a request handler to access the matched value for that parameter. + /// + /// # Percent Encoding and URL Parameters + /// Because each URL parameter is able to capture multiple path segments, both `["%2F", "%25"]` + /// found in the request URI are not decoded into `["/", "%"]` in order to preserve path + /// segment boundaries. If a url parameter is expected to contain these characters, then it is + /// on the user to decode them. #[inline] pub fn match_info(&self) -> &Path { &self.inner.path @@ -161,23 +165,29 @@ impl HttpRequest { self.head().extensions_mut() } - /// Generate url for named resource + /// Generates URL for a named resource. /// + /// This substitutes in sequence all URL parameters that appear in the resource itself and in + /// parent [scopes](crate::web::scope), if any. + /// + /// It is worth noting that the characters `['/', '%']` are not escaped and therefore a single + /// URL parameter may expand into multiple path segments and `elements` can be percent-encoded + /// beforehand without worrying about double encoding. Any other character that is not valid in + /// a URL path context is escaped using percent-encoding. + /// + /// # Examples /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse}; - /// # /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource + /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate URL for "foo" resource /// HttpResponse::Ok().into() /// } /// - /// fn main() { - /// let app = App::new() - /// .service(web::resource("/test/{one}/{two}/{three}") - /// .name("foo") // <- set resource name, then it could be used in `url_for` - /// .route(web::get().to(|| HttpResponse::Ok())) - /// ); - /// } + /// let app = App::new() + /// .service(web::resource("/test/{one}/{two}/{three}") + /// .name("foo") // <- set resource name so it can be used in `url_for` + /// .route(web::get().to(|| HttpResponse::Ok())) + /// ); /// ``` pub fn url_for(&self, name: &str, elements: U) -> Result where @@ -196,8 +206,8 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } - #[inline] /// Get a reference to a `ResourceMap` of current application. + #[inline] pub fn resource_map(&self) -> &ResourceMap { self.app_state().rmap() } diff --git a/src/rmap.rs b/src/rmap.rs index 8466eda28..432eaf83c 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -1,12 +1,14 @@ -use std::cell::RefCell; -use std::rc::{Rc, Weak}; +use std::{ + borrow::Cow, + cell::RefCell, + rc::{Rc, Weak}, +}; use actix_router::ResourceDef; use ahash::AHashMap; use url::Url; -use crate::error::UrlGenerationError; -use crate::request::HttpRequest; +use crate::{error::UrlGenerationError, request::HttpRequest}; #[derive(Clone, Debug)] pub struct ResourceMap { @@ -102,17 +104,28 @@ impl ResourceMap { }) .ok_or(UrlGenerationError::NotEnoughElements)?; - if path.starts_with('/') { + let (base, path): (Cow<'_, _>, _) = if path.starts_with('/') { + // build full URL from connection info parts and resource path let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) + let base = format!("{}://{}", conn.scheme(), conn.host()); + (Cow::Owned(base), path.as_str()) } else { - Ok(Url::parse(&path)?) - } + // external resource; third slash would be the root slash in the path + let third_slash_index = path + .char_indices() + .filter_map(|(i, c)| (c == '/').then(|| i)) + .nth(2) + .unwrap_or_else(|| path.len()); + + ( + Cow::Borrowed(&path[..third_slash_index]), + &path[third_slash_index..], + ) + }; + + let mut url = Url::parse(&base)?; + url.set_path(path); + Ok(url) } pub fn has_resource(&self, path: &str) -> bool { @@ -406,6 +419,42 @@ mod tests { assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); } + #[test] + fn url_for_parser() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut rdef_1 = ResourceDef::new("/{var}"); + rdef_1.set_name("internal"); + + let mut rdef_2 = ResourceDef::new("http://host.dom/{var}"); + rdef_2.set_name("external.1"); + + let mut rdef_3 = ResourceDef::new("{var}"); + rdef_3.set_name("external.2"); + + root.add(&mut rdef_1, None); + root.add(&mut rdef_2, None); + root.add(&mut rdef_3, None); + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + let mut req = crate::test::TestRequest::default(); + req.set_server_hostname("localhost:8888"); + let req = req.to_http_request(); + + const INPUT: &[&str] = &["a/../quick brown%20fox/%nan?query#frag"]; + const OUTPUT: &str = "/quick%20brown%20fox/%nan%3Fquery%23frag"; + + let url = rmap.url_for(&req, "internal", INPUT).unwrap(); + assert_eq!(url.path(), OUTPUT); + + let url = rmap.url_for(&req, "external.1", INPUT).unwrap(); + assert_eq!(url.path(), OUTPUT); + + assert!(rmap.url_for(&req, "external.2", INPUT).is_err()); + assert!(rmap.url_for(&req, "external.2", &[""]).is_err()); + } + #[test] fn external_resource_with_no_name() { let mut root = ResourceMap::new(ResourceDef::prefix("")); From 2d053b703616e19764f4c5c735f282dc0aaaafea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 14:37:20 +0000 Subject: [PATCH 143/861] remove `actix_http::http` module (#2488) --- actix-files/src/named.rs | 5 ++-- actix-http-test/src/lib.rs | 3 ++- actix-http/CHANGES.md | 6 +++-- actix-http/src/encoding/decoder.rs | 2 +- actix-http/src/encoding/encoder.rs | 9 +++---- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 3 +-- actix-http/src/h1/encoder.rs | 6 +++-- actix-http/src/header/map.rs | 42 +++++++++++++++--------------- actix-http/src/lib.rs | 20 -------------- actix-http/src/response.rs | 7 +++-- actix-http/src/response_builder.rs | 16 +++++------- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 7 ++--- actix-http/tests/test_rustls.rs | 7 ++--- actix-http/tests/test_server.rs | 5 ++-- actix-test/src/lib.rs | 5 +--- actix-web-actors/src/ws.rs | 10 +++---- awc/src/builder.rs | 6 ++++- awc/src/client/error.rs | 2 +- awc/src/client/h1proto.rs | 7 ++--- awc/src/error.rs | 5 ++-- awc/src/frozen.rs | 5 ++-- awc/src/lib.rs | 8 +++--- awc/src/middleware/redirect.rs | 11 +++----- awc/src/request.rs | 10 +++---- awc/src/response.rs | 5 ++-- awc/src/sender.rs | 8 +++--- awc/src/test.rs | 9 +++---- awc/src/ws.rs | 21 ++++++++------- awc/tests/test_client.rs | 17 ++++-------- src/app.rs | 9 ++++--- src/extract.rs | 4 +-- src/guard.rs | 36 ++++++++++++------------- src/http/header/allow.rs | 2 +- src/http/header/macros.rs | 2 +- src/http/header/mod.rs | 2 +- src/http/mod.rs | 5 +++- src/middleware/compress.rs | 4 +-- src/middleware/condition.rs | 5 +++- src/middleware/default_headers.rs | 6 ++--- src/middleware/err_handlers.rs | 12 ++++++--- src/middleware/logger.rs | 5 ++-- src/middleware/normalize.rs | 2 +- src/request.rs | 6 ++--- src/resource.rs | 7 +++-- src/responder.rs | 10 +++++-- src/response/builder.rs | 10 +++---- src/response/http_codes.rs | 2 +- src/response/response.rs | 8 +++--- src/route.rs | 2 +- src/scope.rs | 7 +++-- src/service.rs | 5 ++-- src/test.rs | 7 +++-- src/types/query.rs | 6 ++--- src/web.rs | 2 +- tests/test_server.rs | 6 ++--- 57 files changed, 209 insertions(+), 234 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 89775c6b3..0848543a8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -19,9 +19,10 @@ use actix_web::{ }, http::{ header::{ - self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, + self, Charset, ContentDisposition, ContentEncoding, DispositionParam, + DispositionType, ExtendedValue, }, - ContentEncoding, StatusCode, + StatusCode, }, Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 7f55a0bf4..ff86e565a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -13,7 +13,8 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; use actix_server::{Server, ServiceFactory}; use awc::{ - error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector, + error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse, + Connector, }; use bytes::Bytes; use futures_core::stream::Stream; diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 877380581..1a59b233a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -30,13 +30,15 @@ * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max` [#2486] -* `impl TryFrom` for `header::Quality` [#2486] +* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +* `impl TryFrom` for `header::Quality`. [#2486] +* `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 [#1920]: https://github.com/actix/actix-web/pull/1920 [#2486]: https://github.com/actix/actix-web/pull/2486 +[#2488]: https://github.com/actix/actix-web/pull/2488 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index c32983fc7..afe4c6e13 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -23,7 +23,7 @@ use zstd::stream::write::Decoder as ZstdDecoder; use crate::{ encoding::Writer, error::{BlockingError, PayloadError}, - http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, + header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, }; const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 49e5663dc..350e7f062 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -27,11 +27,8 @@ use super::Writer; use crate::{ body::{BodySize, MessageBody}, error::BlockingError, - http::{ - header::{ContentEncoding, CONTENT_ENCODING}, - HeaderValue, StatusCode, - }, - ResponseHead, + header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, + ResponseHead, StatusCode, }; const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024; @@ -222,7 +219,7 @@ where fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { head.headers_mut().insert( - CONTENT_ENCODING, + header::CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str()), ); } diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index f25c35a76..a4db19669 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -511,7 +511,7 @@ mod tests { use super::*; use crate::{ error::ParseError, - http::header::{HeaderName, SET_COOKIE}, + header::{HeaderName, SET_COOKIE}, HttpMessage as _, }; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 6695d1bf3..3c36e7367 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1037,9 +1037,8 @@ mod tests { use crate::{ error::Error, h1::{ExpectHandler, UpgradeHandler}, - http::Method, test::{TestBuffer, TestSeqBuffer}, - HttpMessage, KeepAlive, + HttpMessage, KeepAlive, Method, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 60880cd7d..fccd5da46 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -531,8 +531,10 @@ mod tests { use http::header::AUTHORIZATION; use super::*; - use crate::http::header::{HeaderValue, CONTENT_TYPE}; - use crate::RequestHead; + use crate::{ + header::{HeaderValue, CONTENT_TYPE}, + RequestHead, + }; #[test] fn test_chunked_te() { diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index dd852b021..7b18be991 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -14,7 +14,7 @@ use crate::header::AsHeaderName; /// /// # Examples /// ``` -/// use actix_http::http::{header, HeaderMap, HeaderValue}; +/// use actix_http::header::{self, HeaderMap, HeaderValue}; /// /// let mut map = HeaderMap::new(); /// @@ -75,7 +75,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::new(); /// /// assert!(map.is_empty()); @@ -92,7 +92,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::with_capacity(16); /// /// assert!(map.is_empty()); @@ -139,7 +139,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert_eq!(map.len(), 0); /// @@ -162,7 +162,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert_eq!(map.len_keys(), 0); /// @@ -181,7 +181,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert!(map.is_empty()); /// @@ -198,7 +198,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -231,7 +231,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -264,7 +264,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -293,7 +293,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut none_iter = map.get_all(header::ORIGIN); @@ -319,7 +319,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// assert!(!map.contains_key(header::ACCEPT)); /// @@ -342,7 +342,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -359,7 +359,7 @@ impl HeaderMap { /// A convenience method is provided on the returned iterator to check if the insertion replaced /// any values. /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); @@ -381,7 +381,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.append(header::HOST, HeaderValue::from_static("example.com")); @@ -411,7 +411,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); @@ -430,7 +430,7 @@ impl HeaderMap { /// A convenience method is provided on the returned iterator to check if the `remove` call /// actually removed any values. /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let removed = map.remove("accept"); @@ -459,7 +459,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let map = HeaderMap::with_capacity(16); /// /// assert!(map.is_empty()); @@ -479,7 +479,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::HeaderMap; + /// # use actix_http::header::HeaderMap; /// let mut map = HeaderMap::with_capacity(2); /// assert!(map.capacity() >= 2); /// @@ -499,7 +499,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.iter(); @@ -531,7 +531,7 @@ impl HeaderMap { /// /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.keys(); @@ -559,7 +559,7 @@ impl HeaderMap { /// Keeps the allocated memory for reuse. /// # Examples /// ``` - /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// # use actix_http::header::{self, HeaderMap, HeaderValue}; /// let mut map = HeaderMap::new(); /// /// let mut iter = map.drain(); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index bfb6b8c55..aeba3da36 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -67,26 +67,6 @@ pub use self::service::HttpService; pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; -// TODO: deprecate this mish-mash of random items -pub mod http { - //! Various HTTP related types. - - // re-exports - pub use http::header::{HeaderName, HeaderValue}; - pub use http::uri::PathAndQuery; - pub use http::{uri, Error, Uri}; - pub use http::{Method, StatusCode, Version}; - - pub use crate::header::HeaderMap; - - /// A collection of HTTP headers and helpers. - pub mod header { - pub use crate::header::*; - } - pub use crate::header::ContentEncoding; - pub use crate::message::ConnectionType; -} - /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ad41094ae..ee7e38913 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -11,10 +11,9 @@ use bytestring::ByteString; use crate::{ body::{BoxBody, MessageBody}, extensions::Extensions, - header::{self, IntoHeaderValue}, - http::{HeaderMap, StatusCode}, + header::{self, HeaderMap, IntoHeaderValue}, message::{BoxedResponseHead, ResponseHead}, - Error, ResponseBuilder, + Error, ResponseBuilder, StatusCode, }; /// An HTTP response. @@ -323,7 +322,7 @@ mod tests { use super::*; use crate::{ body::to_bytes, - http::header::{HeaderValue, CONTENT_TYPE, COOKIE}, + header::{HeaderValue, CONTENT_TYPE, COOKIE}, }; #[test] diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index 0537112d5..f11f89219 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -20,7 +20,7 @@ use crate::{ /// /// # Examples /// ``` -/// use actix_http::{Response, ResponseBuilder, body, http::StatusCode, http::header}; +/// use actix_http::{Response, ResponseBuilder, StatusCode, body, header}; /// /// # actix_rt::System::new().block_on(async { /// let mut res: Response<_> = Response::build(StatusCode::OK) @@ -47,9 +47,7 @@ impl ResponseBuilder { /// Create response builder /// /// # Examples - /// ``` - /// use actix_http::{Response, ResponseBuilder, http::StatusCode}; - /// + // /// use actix_http::{Response, ResponseBuilder, StatusCode};, / `` /// let res: Response<_> = ResponseBuilder::default().finish(); /// assert_eq!(res.status(), StatusCode::OK); /// ``` @@ -64,9 +62,7 @@ impl ResponseBuilder { /// Set HTTP status code of this response. /// /// # Examples - /// ``` - /// use actix_http::{ResponseBuilder, http::StatusCode}; - /// + // /// use actix_http::{ResponseBuilder, StatusCode};, / `` /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// ``` @@ -82,7 +78,7 @@ impl ResponseBuilder { /// /// # Examples /// ``` - /// use actix_http::{ResponseBuilder, http::header}; + /// use actix_http::{ResponseBuilder, header}; /// /// let res = ResponseBuilder::default() /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) @@ -112,7 +108,7 @@ impl ResponseBuilder { /// /// # Examples /// ``` - /// use actix_http::{ResponseBuilder, http::header}; + /// use actix_http::{ResponseBuilder, header}; /// /// let res = ResponseBuilder::default() /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) @@ -335,7 +331,7 @@ mod tests { use bytes::Bytes; use super::*; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; + use crate::header::{HeaderName, HeaderValue, CONTENT_TYPE}; #[test] fn test_basic_builder() { diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 4c923873f..acbdc8e83 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use actix_http::{ - body::BoxBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response, + body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode, }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 86ee17c74..6f68cc04d 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -7,11 +7,8 @@ use std::{convert::Infallible, io}; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, - http::{ - header::{self, HeaderValue}, - Method, StatusCode, Version, - }, - Error, HttpMessage, HttpService, Request, Response, + header::{self, HeaderValue}, + Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 873752779..1fc3bdf49 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -12,11 +12,8 @@ use std::{ use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, - http::{ - header::{self, HeaderName, HeaderValue}, - Method, StatusCode, Version, - }, - Error, HttpService, Request, Response, + header::{self, HeaderName, HeaderValue}, + Error, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index adf2a28ca..e6733b29b 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -7,8 +7,7 @@ use std::{ use actix_http::{ body::{self, BodyStream, BoxBody, SizedStream}, - header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response, - StatusCode, + header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode, }; use actix_http_test::test_server; use actix_rt::time::sleep; @@ -383,7 +382,7 @@ async fn test_http1_keepalive_disabled() { #[actix_rt::test] async fn test_content_length() { - use actix_http::http::{ + use actix_http::{ header::{HeaderName, HeaderValue}, StatusCode, }; diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 1decd6e98..7e493ce71 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -35,10 +35,7 @@ use std::{fmt, net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; pub use actix_http::test::TestBuffer; -use actix_http::{ - http::{HeaderMap, Method}, - ws, HttpService, Request, Response, -}; +use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_web::{ body::MessageBody, diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index c41268b01..6fde10192 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -19,16 +19,16 @@ use actix::{ SpawnHandle, }; use actix_codec::{Decoder as _, Encoder as _}; +use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; -use actix_http::{ - http::HeaderValue, - ws::{hash_key, Codec}, -}; use actix_web::{ error::{Error, PayloadError}, - http::{header, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, HttpRequest, HttpResponse, HttpResponseBuilder, }; use bytes::{Bytes, BytesMut}; diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 70a28c419..43e5c0def 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,6 +1,10 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration}; -use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}; +use actix_http::{ + error::HttpError, + header::{self, HeaderMap, HeaderName}, + Uri, +}; use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; diff --git a/awc/src/client/error.rs b/awc/src/client/error.rs index d351b5d5e..9f290c5c0 100644 --- a/awc/src/client/error.rs +++ b/awc/src/client/error.rs @@ -2,7 +2,7 @@ use std::{fmt, io}; use derive_more::{Display, From}; -use actix_http::{error::ParseError, http::Error as HttpError}; +use actix_http::error::{HttpError, ParseError}; #[cfg(feature = "openssl")] use actix_tls::accept::openssl::reexports::Error as OpensslError; diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index b26a97eeb..c8b9a3fae 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -9,11 +9,8 @@ use actix_http::{ body::{BodySize, MessageBody}, error::PayloadError, h1, - http::{ - header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, - StatusCode, - }, - Payload, RequestHeadType, ResponseHead, + header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, + Payload, RequestHeadType, ResponseHead, StatusCode, }; use actix_utils::future::poll_fn; use bytes::buf::BufMut; diff --git a/awc/src/error.rs b/awc/src/error.rs index 726e1a506..c1d855053 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,9 +1,10 @@ //! HTTP client errors pub use actix_http::{ - error::PayloadError, - http::{header::HeaderValue, Error as HttpError, StatusCode}, + error::{HttpError, PayloadError}, + header::HeaderValue, ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError}, + StatusCode, }; use derive_more::{Display, From}; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 472397359..7497f85c8 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -5,8 +5,9 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, - RequestHead, + error::HttpError, + header::{HeaderMap, HeaderName, IntoHeaderValue}, + Method, RequestHead, Uri, }; use crate::{ diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 2f4183120..0cb6c7f4f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -117,7 +117,8 @@ mod sender; pub mod test; pub mod ws; -pub use actix_http::http; +// TODO: hmmmmmm +pub use actix_http as http; #[cfg(feature = "cookies")] pub use cookie; @@ -131,10 +132,7 @@ pub use self::sender::SendClientRequest; use std::{convert::TryFrom, rc::Rc, time::Duration}; -use actix_http::{ - http::{Error as HttpError, HeaderMap, Method, Uri}, - RequestHead, -}; +use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri}; use actix_rt::net::TcpStream; use actix_service::Service; diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 89cff22cd..0fde48074 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -7,10 +7,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::{ - http::{header, Method, StatusCode, Uri}, - RequestHead, RequestHeadType, -}; +use actix_http::{header, Method, RequestHead, RequestHeadType, StatusCode, Uri}; use actix_service::Service; use bytes::Bytes; use futures_core::ready; @@ -284,12 +281,12 @@ fn remove_sensitive_headers(headers: &mut header::HeaderMap, prev_uri: &Uri, nex #[cfg(test)] mod tests { + use std::str::FromStr; + use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use super::*; - use crate::http::HeaderValue; - use crate::ClientBuilder; - use std::str::FromStr; + use crate::{http::header::HeaderValue, ClientBuilder}; #[actix_rt::test] async fn test_basic_redirect() { diff --git a/awc/src/request.rs b/awc/src/request.rs index d26b703f6..3e1f83a82 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -5,11 +5,9 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ - http::{ - header::{self, IntoHeaderPair}, - ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, - }, - RequestHead, + error::HttpError, + header::{self, HeaderMap, HeaderValue, IntoHeaderPair}, + ConnectionType, Method, RequestHead, Uri, Version, }; use crate::{ @@ -539,7 +537,7 @@ impl fmt::Debug for ClientRequest { mod tests { use std::time::SystemTime; - use actix_http::http::header::HttpDate; + use actix_http::header::HttpDate; use super::*; use crate::Client; diff --git a/awc/src/response.rs b/awc/src/response.rs index a966edd08..fefebd0a0 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -10,9 +10,8 @@ use std::{ }; use actix_http::{ - error::PayloadError, - http::{header, HeaderMap, StatusCode, Version}, - Extensions, HttpMessage, Payload, PayloadStream, ResponseHead, + error::PayloadError, header, header::HeaderMap, Extensions, HttpMessage, Payload, + PayloadStream, ResponseHead, StatusCode, Version, }; use actix_rt::time::{sleep, Sleep}; use bytes::{Bytes, BytesMut}; diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 51fce1913..1faf6140a 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -9,10 +9,8 @@ use std::{ use actix_http::{ body::BodyStream, - http::{ - header::{self, HeaderMap, HeaderName, IntoHeaderValue}, - Error as HttpError, - }, + error::HttpError, + header::{self, HeaderMap, HeaderName, IntoHeaderValue}, RequestHead, RequestHeadType, }; use actix_rt::time::{sleep, Sleep}; @@ -22,7 +20,7 @@ use futures_core::Stream; use serde::Serialize; #[cfg(feature = "__compress")] -use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; +use actix_http::{encoding::Decoder, header::ContentEncoding, Payload, PayloadStream}; use crate::{ any_body::AnyBody, diff --git a/awc/src/test.rs b/awc/src/test.rs index 1abe78811..4a5c8e7ea 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,7 +1,6 @@ //! Test helpers for actix http client to use during testing. -use actix_http::http::header::IntoHeaderPair; -use actix_http::http::{StatusCode, Version}; -use actix_http::{h1, Payload, ResponseHead}; + +use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version}; use bytes::Bytes; #[cfg(feature = "cookies")] @@ -89,7 +88,7 @@ impl TestResponse { #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { - use actix_http::http::header::{self, HeaderValue}; + use actix_http::header::{self, HeaderValue}; head.headers.insert( header::SET_COOKIE, @@ -109,7 +108,7 @@ impl TestResponse { mod tests { use std::time::SystemTime; - use actix_http::http::header::HttpDate; + use actix_http::header::HttpDate; use super::*; use crate::{cookie, http::header}; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index e2f1f86d0..f0d421dbc 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -26,9 +26,7 @@ //! } //! ``` -use std::convert::TryFrom; -use std::net::SocketAddr; -use std::{fmt, str}; +use std::{convert::TryFrom, fmt, net::SocketAddr, str}; use actix_codec::Framed; use actix_http::{ws, Payload, RequestHead}; @@ -37,14 +35,19 @@ use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; -use crate::connect::{BoxedSocket, ConnectRequest}; +use crate::{ + connect::{BoxedSocket, ConnectRequest}, + error::{HttpError, InvalidUrl, SendRequestError, WsClientError}, + http::{ + header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}, + ConnectionType, Method, StatusCode, Uri, Version, + }, + response::ClientResponse, + ClientConfig, +}; + #[cfg(feature = "cookies")] use crate::cookie::{Cookie, CookieJar}; -use crate::error::{InvalidUrl, SendRequestError, WsClientError}; -use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}; -use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version}; -use crate::response::ClientResponse; -use crate::ClientConfig; /// WebSocket connection. pub struct WebsocketsRequest { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 5abb63e39..c453a768d 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -21,10 +21,7 @@ use brotli2::write::BrotliEncoder; #[cfg(feature = "compress-gzip")] use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use actix_http::{ - http::{self, StatusCode}, - HttpService, -}; +use actix_http::{ContentEncoding, HttpService, StatusCode}; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ @@ -647,9 +644,7 @@ async fn test_client_brotli_encoding_large_random() { async fn test_client_deflate_encoding() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encoding(http::ContentEncoding::Br) - .body(body) + HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) })) }); @@ -672,9 +667,7 @@ async fn test_client_deflate_encoding_large_random() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encoding(http::ContentEncoding::Br) - .body(body) + HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) })) }); @@ -692,7 +685,7 @@ async fn test_client_streaming_explicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .streaming(body) })) }); @@ -717,7 +710,7 @@ async fn test_body_streaming_implicit() { }); HttpResponse::Ok() - .encoding(http::ContentEncoding::Gzip) + .encoding(ContentEncoding::Gzip) .streaming(Box::pin(body)) })) }); diff --git a/src/app.rs b/src/app.rs index efc108cb9..a27dd54a6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -353,7 +353,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{middleware, web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -410,7 +410,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -494,7 +494,10 @@ mod tests { use bytes::Bytes; use super::*; - use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest}; diff --git a/src/extract.rs b/src/extract.rs index bb2dabb9f..f74a0a54e 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::http::{Method, Uri}; +use actix_http::{Method, Uri}; use actix_utils::future::{ok, Ready}; use futures_core::ready; use pin_project_lite::pin_project; @@ -402,7 +402,7 @@ mod tuple_from_req { #[cfg(test)] mod tests { - use actix_http::http::header; + use actix_http::header; use bytes::Bytes; use serde::Deserialize; diff --git a/src/guard.rs b/src/guard.rs index c71d64a29..a5770df89 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -24,13 +24,13 @@ //! ); //! } //! ``` -#![allow(non_snake_case)] -use std::convert::TryFrom; -use std::ops::Deref; -use std::rc::Rc; -use actix_http::http::{self, header, uri::Uri}; -use actix_http::RequestHead; +#![allow(non_snake_case)] + +use std::rc::Rc; +use std::{convert::TryFrom, ops::Deref}; + +use actix_http::{header, uri::Uri, Method as HttpMethod, RequestHead}; /// Trait defines resource guards. Guards are used for route selection. /// @@ -186,7 +186,7 @@ impl Guard for NotGuard { /// HTTP method guard. #[doc(hidden)] -pub struct MethodGuard(http::Method); +pub struct MethodGuard(HttpMethod); impl Guard for MethodGuard { fn check(&self, request: &RequestHead) -> bool { @@ -196,51 +196,51 @@ impl Guard for MethodGuard { /// Guard to match *GET* HTTP method. pub fn Get() -> MethodGuard { - MethodGuard(http::Method::GET) + MethodGuard(HttpMethod::GET) } /// Predicate to match *POST* HTTP method. pub fn Post() -> MethodGuard { - MethodGuard(http::Method::POST) + MethodGuard(HttpMethod::POST) } /// Predicate to match *PUT* HTTP method. pub fn Put() -> MethodGuard { - MethodGuard(http::Method::PUT) + MethodGuard(HttpMethod::PUT) } /// Predicate to match *DELETE* HTTP method. pub fn Delete() -> MethodGuard { - MethodGuard(http::Method::DELETE) + MethodGuard(HttpMethod::DELETE) } /// Predicate to match *HEAD* HTTP method. pub fn Head() -> MethodGuard { - MethodGuard(http::Method::HEAD) + MethodGuard(HttpMethod::HEAD) } /// Predicate to match *OPTIONS* HTTP method. pub fn Options() -> MethodGuard { - MethodGuard(http::Method::OPTIONS) + MethodGuard(HttpMethod::OPTIONS) } /// Predicate to match *CONNECT* HTTP method. pub fn Connect() -> MethodGuard { - MethodGuard(http::Method::CONNECT) + MethodGuard(HttpMethod::CONNECT) } /// Predicate to match *PATCH* HTTP method. pub fn Patch() -> MethodGuard { - MethodGuard(http::Method::PATCH) + MethodGuard(HttpMethod::PATCH) } /// Predicate to match *TRACE* HTTP method. pub fn Trace() -> MethodGuard { - MethodGuard(http::Method::TRACE) + MethodGuard(HttpMethod::TRACE) } /// Predicate to match specified HTTP method. -pub fn Method(method: http::Method) -> MethodGuard { +pub fn Method(method: HttpMethod) -> MethodGuard { MethodGuard(method) } @@ -331,7 +331,7 @@ impl Guard for HostGuard { #[cfg(test)] mod tests { - use actix_http::http::{header, Method}; + use actix_http::{header, Method}; use super::*; use crate::test::TestRequest; diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index c8cc153e8..d0ef96486 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,4 +1,4 @@ -use actix_http::http::Method; +use actix_http::Method; use crate::http::header; diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 3f530658c..ca3792a37 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -6,7 +6,7 @@ macro_rules! common_header_test_module { use ::core::str; - use ::actix_http::{http::Method, test}; + use ::actix_http::{Method, test}; use ::mime::*; use $crate::http::header::{self, *}; diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 07b7592d7..9807d5f5e 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -15,7 +15,7 @@ use bytes::{Bytes, BytesMut}; // - header map // - the few typed headers from actix-http // - header parsing utils -pub use actix_http::http::header::*; +pub use actix_http::header::*; mod accept; mod accept_charset; diff --git a/src/http/mod.rs b/src/http/mod.rs index fa28a5fa9..bbd94a60f 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,2 +1,5 @@ +//! Various HTTP related types. + pub mod header; -pub use actix_http::http::*; + +pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version}; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d017e9a5a..af4a107e3 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -2,7 +2,7 @@ use std::{ cmp, - convert::TryFrom, + convert::TryFrom as _, future::Future, marker::PhantomData, pin::Pin, @@ -12,7 +12,7 @@ use std::{ use actix_http::{ body::{EitherBody, MessageBody}, encoding::Encoder, - http::header::{ContentEncoding, ACCEPT_ENCODING}, + header::{ContentEncoding, ACCEPT_ENCODING}, StatusCode, }; use actix_service::{Service, Transform}; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index d1ba7ee4d..a7777a96b 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -102,7 +102,10 @@ mod tests { use crate::{ dev::{ServiceRequest, ServiceResponse}, error::Result, - http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, middleware::err_handlers::*, test::{self, TestRequest}, HttpResponse, diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 426810247..dceca44c2 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -9,16 +9,14 @@ use std::{ task::{Context, Poll}, }; +use actix_http::error::HttpError; use actix_utils::future::{ready, Ready}; use futures_core::ready; use pin_project_lite::pin_project; use crate::{ dev::{Service, Transform}, - http::{ - header::{HeaderName, HeaderValue, CONTENT_TYPE}, - Error as HttpError, HeaderMap, - }, + http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}, service::{ServiceRequest, ServiceResponse}, Error, }; diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 1a834c1e8..756da30c3 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -37,19 +37,20 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result(mut res: dev::ServiceResponse) -> Result> { /// res.response_mut() /// .headers_mut() -/// .insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("Error")); +/// .insert(header::CONTENT_TYPE, header::HeaderValue::from_static("Error")); /// Ok(ErrorHandlerResponse::Response(res)) /// } /// /// let app = App::new() /// .wrap( /// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), +/// .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), /// ) /// .service(web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) @@ -182,7 +183,10 @@ mod tests { use futures_util::future::FutureExt as _; use super::*; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; + use crate::http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }; use crate::test::{self, TestRequest}; use crate::HttpResponse; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f89b13a1c..74daa26d5 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -23,7 +23,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ body::{BodySize, MessageBody}, - http::HeaderName, + http::header::HeaderName, service::{ServiceRequest, ServiceResponse}, Error, HttpResponse, Result, }; @@ -126,7 +126,8 @@ impl Logger { /// /// # Example /// ``` - /// # use actix_web::{http::HeaderValue, middleware::Logger}; + /// # use actix_web::http::{header::HeaderValue}; + /// # use actix_web::middleware::Logger; /// # fn parse_jwt_id (_req: Option<&HeaderValue>) -> String { "jwt_uid".to_owned() } /// Logger::new("example %{JWT_ID}xi") /// .custom_request_replace("JWT_ID", |req| parse_jwt_id(req.headers().get("Authorization"))); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 8ad0bb3f0..18dcaeefa 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,6 +1,6 @@ //! For middleware documentation, see [`NormalizePath`]. -use actix_http::http::{PathAndQuery, Uri}; +use actix_http::uri::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use actix_utils::future::{ready, Ready}; use bytes::Bytes; diff --git a/src/request.rs b/src/request.rs index 58222da47..f04d47c6f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,8 +6,8 @@ use std::{ }; use actix_http::{ - http::{HeaderMap, Method, Uri, Version}, - Extensions, HttpMessage, Message, Payload, RequestHead, + header::HeaderMap, Extensions, HttpMessage, Message, Method, Payload, RequestHead, Uri, + Version, }; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; @@ -266,7 +266,7 @@ impl HttpRequest { /// Load request cookies. #[cfg(feature = "cookies")] pub fn cookies(&self) -> Result>>, CookieParseError> { - use actix_http::http::header::COOKIE; + use actix_http::header::COOKIE; if self.extensions().get::().is_none() { let mut cookies = Vec::new(); diff --git a/src/resource.rs b/src/resource.rs index fc417bac2..420374a86 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -298,7 +298,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -508,7 +508,10 @@ mod tests { use crate::{ guard, - http::{header, HeaderValue, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{call_service, init_service, TestRequest}, diff --git a/src/responder.rs b/src/responder.rs index 9d8a0e8ed..e72739a71 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -2,7 +2,10 @@ use std::borrow::Cow; use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, + error::HttpError, + header::HeaderMap, + header::IntoHeaderPair, + StatusCode, }; use bytes::{Bytes, BytesMut}; @@ -280,7 +283,10 @@ pub(crate) mod tests { use super::*; use crate::{ error, - http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, test::{assert_body_eq, init_service, TestRequest}, web, App, }; diff --git a/src/response/builder.rs b/src/response/builder.rs index b5bef2e99..50e23f81b 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -8,18 +8,16 @@ use std::{ use actix_http::{ body::{BodyStream, BoxBody, MessageBody}, - http::{ - header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, - ConnectionType, Error as HttpError, StatusCode, - }, - Extensions, Response, ResponseHead, + error::HttpError, + header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, + ConnectionType, Extensions, Response, ResponseHead, StatusCode, }; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; #[cfg(feature = "cookies")] -use actix_http::http::header::HeaderValue; +use actix_http::header::HeaderValue; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; diff --git a/src/response/http_codes.rs b/src/response/http_codes.rs index 44ddb78f9..986735346 100644 --- a/src/response/http_codes.rs +++ b/src/response/http_codes.rs @@ -1,6 +1,6 @@ //! Status code based HTTP response builders. -use actix_http::http::StatusCode; +use actix_http::StatusCode; use crate::{HttpResponse, HttpResponseBuilder}; diff --git a/src/response/response.rs b/src/response/response.rs index 97de21e42..1900dd845 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -9,15 +9,15 @@ use std::{ use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{header::HeaderMap, StatusCode}, - Extensions, Response, ResponseHead, + header::HeaderMap, + Extensions, Response, ResponseHead, StatusCode, }; #[cfg(feature = "cookies")] use { - actix_http::http::{ + actix_http::{ + error::HttpError, header::{self, HeaderValue}, - Error as HttpError, }, cookie::Cookie, }; diff --git a/src/route.rs b/src/route.rs index 1eb323068..4447bff50 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,6 +1,6 @@ use std::{future::Future, mem, rc::Rc}; -use actix_http::http::Method; +use actix_http::Method; use actix_service::{ boxed::{self, BoxService}, fn_service, Service, ServiceFactory, ServiceFactoryExt, diff --git a/src/scope.rs b/src/scope.rs index ff013671b..ad102b66b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -347,7 +347,7 @@ where /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" @@ -587,7 +587,10 @@ mod tests { use crate::{ guard, - http::{header, HeaderValue, Method, StatusCode}, + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, diff --git a/src/service.rs b/src/service.rs index df9e809e4..4185d6018 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,8 +6,9 @@ use std::{ use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - http::{HeaderMap, Method, StatusCode, Uri, Version}, - Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, + header::HeaderMap, + Extensions, HttpMessage, Method, Payload, PayloadStream, RequestHead, Response, + ResponseHead, StatusCode, Uri, Version, }; use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url}; use actix_service::{ diff --git a/src/test.rs b/src/test.rs index 2cd01039d..07d2d16b6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,9 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, - test::TestRequest as HttpTestRequest, - Extensions, Request, + header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request, + StatusCode, Uri, Version, }; use actix_router::{Path, ResourceDef, Url}; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; @@ -547,7 +546,7 @@ impl TestRequest { #[cfg(feature = "cookies")] { - use actix_http::http::header::{HeaderValue, COOKIE}; + use actix_http::header::{HeaderValue, COOKIE}; let cookie: String = self .cookies diff --git a/src/types/query.rs b/src/types/query.rs index 9fac21173..97d17123d 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -185,14 +185,12 @@ impl QueryConfig { #[cfg(test)] mod tests { - use actix_http::http::StatusCode; + use actix_http::StatusCode; use derive_more::Display; use serde::Deserialize; use super::*; - use crate::error::InternalError; - use crate::test::TestRequest; - use crate::HttpResponse; + use crate::{error::InternalError, test::TestRequest, HttpResponse}; #[derive(Deserialize, Debug, Display)] struct Id { diff --git a/src/web.rs b/src/web.rs index b58adc2f8..16dbace60 100644 --- a/src/web.rs +++ b/src/web.rs @@ -2,7 +2,7 @@ use std::{error::Error as StdError, future::Future}; -use actix_http::http::Method; +use actix_http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; diff --git a/tests/test_server.rs b/tests/test_server.rs index a850f228d..51a78eb28 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,7 +10,7 @@ use std::{ task::{Context, Poll}, }; -use actix_http::http::header::{ +use actix_http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -902,7 +902,7 @@ async fn test_brotli_encoding_large_openssl() { actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .body(bytes) }))) }); @@ -970,7 +970,7 @@ mod plus_rustls { let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) + .encoding(ContentEncoding::Identity) .body(bytes) }))) }); From 627c0dc22fa580cc36dbb0a1d1b186468fd5a103 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sun, 5 Dec 2021 19:19:08 +0300 Subject: [PATCH 144/861] workaround rustdoc bug for Error (#2489) --- src/error/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index 46d0dccc6..90c2c9a61 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,6 +1,12 @@ //! Error and Result module -pub use actix_http::error::*; +/// This is meant to be a glob import of the whole error module, but rustdoc can't handle +/// shadowing `Error` type, so it is expanded manually. +/// See https://github.com/rust-lang/rust/issues/83375 +pub use actix_http::error::{ + BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, +}; + use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; use serde_urlencoded::de::Error as FormDeError; From c596f573a6bdde10e7cb12256e9ed05eea4bab9b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 21:25:15 +0000 Subject: [PATCH 145/861] bump actix-server to rc.1 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 425bdbbb3..cee0680a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ experimental-io-uring = ["actix-server/io-uring"] actix-codec = "0.4.1" actix-macros = "0.2.3" actix-rt = "2.3" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 8d347d4e9..7a22cbcc1 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -34,7 +34,7 @@ actix-codec = "0.4.1" actix-tls = "3.0.0-rc.1" actix-utils = "3.0.0" actix-rt = "2.2" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" awc = { version = "3.0.0-beta.11", default-features = false } base64 = "0.13" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 967f04d03..87669aeb1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } async-stream = "0.3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index fc60f5edb..836241d46 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -97,7 +97,7 @@ actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } actix-http = { version = "3.0.0-beta.14", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" -actix-server = "2.0.0-beta.9" +actix-server = "2.0.0-rc.1" actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } From bed72d9bb7db33b94018cf65d7fc286e4dad1f76 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 5 Dec 2021 23:23:36 +0000 Subject: [PATCH 146/861] fix examples --- actix-http/examples/echo.rs | 2 +- actix-http/examples/echo2.rs | 4 ++-- actix-http/examples/hello-world.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 6cfe3a675..5ff2bcc89 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,6 +1,6 @@ use std::io; -use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; +use actix_http::{Error, HttpService, Request, Response, StatusCode}; use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 6092c01ce..487b8d8d1 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,8 +1,8 @@ use std::io; use actix_http::{ - body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request, - Response, + body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, + StatusCode, }; use actix_server::Server; use bytes::BytesMut; diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 9a593c66a..3678774b8 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,6 +1,6 @@ use std::{convert::Infallible, io}; -use actix_http::{http::StatusCode, HttpService, Response}; +use actix_http::{HttpService, Response, StatusCode}; use actix_server::Server; use http::header::HeaderValue; From 606a371ec3dd28391fd4c1b8fce5a50d5650dd7c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 6 Dec 2021 17:14:56 +0000 Subject: [PATCH 147/861] improve Data docs --- src/data.rs | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/data.rs b/src/data.rs index b29e4ecf4..ef077e87c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -31,41 +31,53 @@ pub(crate) type FnDataFactory = /// server constructs an application instance for each thread, thus application data must be /// constructed multiple times. If you want to share data between different threads, a shareable /// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send` -/// or `Sync`. Internally `Data` uses `Arc`. +/// or `Sync`. Internally `Data` contains an `Arc`. /// -/// If route data is not set for a handler, using `Data` extractor would cause *Internal -/// Server Error* response. +/// If route data is not set for a handler, using `Data` extractor would cause a `500 Internal +/// Server Error` response. /// -// TODO: document `dyn T` functionality through converting an Arc -// TODO: note equivalence of req.app_data> and Data extractor -// TODO: note that data must be inserted using Data in order to extract it +/// # Unsized Data +/// For types that are unsized, most commonly `dyn T`, `Data` can wrap these types by first +/// constructing an `Arc` and using the `From` implementation to convert it. +/// +/// ``` +/// # use std::{fmt::Display, sync::Arc}; +/// # use actix_web::web::Data; +/// let displayable_arc: Arc = Arc::new(42usize); +/// let displayable_data: Data = Data::from(displayable_arc); +/// ``` /// /// # Examples /// ``` /// use std::sync::Mutex; -/// use actix_web::{web, App, HttpResponse, Responder}; +/// use actix_web::{App, HttpRequest, HttpResponse, Responder, web::{self, Data}}; /// /// struct MyData { /// counter: usize, /// } /// /// /// Use the `Data` extractor to access data in a handler. -/// async fn index(data: web::Data>) -> impl Responder { -/// let mut data = data.lock().unwrap(); -/// data.counter += 1; +/// async fn index(data: Data>) -> impl Responder { +/// let mut my_data = data.lock().unwrap(); +/// my_data.counter += 1; /// HttpResponse::Ok() /// } /// -/// fn main() { -/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 })); -/// -/// let app = App::new() -/// // Store `MyData` in application storage. -/// .app_data(data.clone()) -/// .service( -/// web::resource("/index.html").route( -/// web::get().to(index))); +/// /// Alteratively, use the `HttpRequest::app_data` method to access data in a handler. +/// async fn index_alt(req: HttpRequest) -> impl Responder { +/// let data = req.app_data::>>().unwrap(); +/// let mut my_data = data.lock().unwrap(); +/// my_data.counter += 1; +/// HttpResponse::Ok() /// } +/// +/// let data = Data::new(Mutex::new(MyData { counter: 0 })); +/// +/// let app = App::new() +/// // Store `MyData` in application storage. +/// .app_data(Data::clone(&data)) +/// .route("/index.html", web::get().to(index)) +/// .route("/index-alt.html", web::get().to(index_alt)); /// ``` #[derive(Debug)] pub struct Data(Arc); From 9587261c2099ea46182147c7f8807f8b1055448e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 7 Dec 2021 15:31:15 +0000 Subject: [PATCH 148/861] add fakeshadow's actix-web in actix-http example --- actix-http/Cargo.toml | 3 ++- actix-http/examples/actix-web.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 actix-http/examples/actix-web.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 87669aeb1..6216af3d1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -84,6 +84,7 @@ zstd = { version = "0.9", optional = true } actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } +actix-web = "4.0.0-beta.13" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" @@ -95,7 +96,7 @@ serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -tokio = { version = "1.2", features = ["net", "rt"] } +tokio = { version = "1.2", features = ["net", "rt", "macros"] } [[example]] name = "ws" diff --git a/actix-http/examples/actix-web.rs b/actix-http/examples/actix-web.rs new file mode 100644 index 000000000..f8226507f --- /dev/null +++ b/actix-http/examples/actix-web.rs @@ -0,0 +1,26 @@ +use actix_http::HttpService; +use actix_server::Server; +use actix_service::map_config; +use actix_web::{dev::AppConfig, get, App}; + +#[get("/")] +async fn index() -> &'static str { + "Hello, world. From Actix Web!" +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> std::io::Result<()> { + Server::build() + .bind("hello-world", "127.0.0.1:8080", || { + // construct actix-web app + let app = App::new().service(index); + + HttpService::build() + // pass the app to service builder + // map_config is used to map App's configuration to ServiceBuilder + .finish(map_config(app, |_| AppConfig::default())) + .tcp() + })? + .run() + .await +} From 6460e67f8469aae30799fd2b740d7c2ba9449941 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 7 Dec 2021 23:53:04 +0800 Subject: [PATCH 149/861] remove generic body type in App. (#2493) --- src/app.rs | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/app.rs b/src/app.rs index a27dd54a6..ab2081c18 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, rc::Rc}; -use actix_http::{ - body::{BoxBody, MessageBody}, - Extensions, Request, -}; +use actix_http::{body::MessageBody, Extensions, Request}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, @@ -26,7 +23,7 @@ use crate::{ /// Application builder - structure that follows the builder pattern /// for building application instances. -pub struct App { +pub struct App { endpoint: T, services: Vec>, default: Option>, @@ -34,10 +31,9 @@ pub struct App { data_factories: Vec, external: Vec, extensions: Extensions, - _phantom: PhantomData, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -51,22 +47,11 @@ impl App { factory_ref, external: Vec::new(), extensions: Extensions::new(), - _phantom: PhantomData, } } } -impl App -where - B: MessageBody, - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ +impl App { /// Set application (root level) data. /// /// Application data stored with `App::app_data()` method is available through the @@ -365,7 +350,7 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap( + pub fn wrap( self, mw: M, ) -> App< @@ -376,9 +361,16 @@ where Error = Error, InitError = (), >, - B1, > where + T: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + Config = (), + InitError = (), + >, + B: MessageBody, M: Transform< T::Service, ServiceRequest, @@ -396,7 +388,6 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _phantom: PhantomData, } } @@ -431,7 +422,7 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> App< @@ -442,12 +433,19 @@ where Error = Error, InitError = (), >, - B1, > where - B1: MessageBody, + T: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + Config = (), + InitError = (), + >, + B: MessageBody, F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future, Error>>, + B1: MessageBody, { App { endpoint: apply_fn_factory(self.endpoint, mw), @@ -457,12 +455,11 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _phantom: PhantomData, } } } -impl IntoServiceFactory, Request> for App +impl IntoServiceFactory, Request> for App where B: MessageBody, T: ServiceFactory< From 069cf2da0792d7a2160e4d6a5345104c80aa7967 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 8 Dec 2021 00:26:28 +0800 Subject: [PATCH 150/861] enable scope middleware with generic res body. (#2492) Co-authored-by: Rob Ede --- CHANGES.md | 4 ++++ src/middleware/compat.rs | 2 +- src/scope.rs | 49 ++++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1b108fee6..2adf54d3d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ * Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] +* Remove `B` (body) type parameter on `App`. [#2493] +* Add `B` (body) type parameter on `Scope`. [#2492] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] @@ -25,6 +27,8 @@ [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 +[#2492]: https://github.com/actix/actix-web/pull/2492 +[#2493]: https://github.com/actix/actix-web/pull/2493 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index e6ef1806f..ed441f7b9 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -154,7 +154,7 @@ mod tests { let srv = init_service( App::new().service( web::scope("app") - .wrap(Compat::new(logger)) + .wrap(logger) .wrap(Compat::new(compress)) .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), diff --git a/src/scope.rs b/src/scope.rs index ad102b66b..74523cd94 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc}; -use actix_http::Extensions; +use actix_http::{body::BoxBody, Extensions}; use actix_router::{ResourceDef, Router}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, @@ -52,7 +52,7 @@ type Guards = Vec>; /// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests -pub struct Scope { +pub struct Scope { endpoint: T, rdef: String, app_data: Option, @@ -61,6 +61,7 @@ pub struct Scope { default: Option>, external: Vec, factory_ref: Rc>>, + _phantom: PhantomData, } impl Scope { @@ -77,19 +78,21 @@ impl Scope { default: None, external: Vec::new(), factory_ref, + _phantom: Default::default(), } } } -impl Scope +impl Scope where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B: 'static, { /// Add match guard to a scope. /// @@ -295,32 +298,29 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound processing in the request - /// life-cycle (request -> response), modifying request as - /// necessary, across all requests managed by the *Scope*. Scope-level - /// middleware is more limited in what it can modify, relative to Route or - /// Application level middleware, in that Scope-level middleware can not modify - /// ServiceResponse. + /// Registers middleware, in the form of a middleware component (type), that runs during inbound + /// processing in the request life-cycle (request -> response), modifying request as necessary, + /// across all requests managed by the *Scope*. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( + pub fn wrap( self, mw: M, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where M: Transform< T::Service, ServiceRequest, - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, @@ -334,16 +334,15 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, + _phantom: PhantomData, } } - /// Registers middleware, in the form of a closure, that runs during inbound - /// processing in the request life-cycle (request -> response), modifying - /// request as necessary, across all requests managed by the *Scope*. - /// Scope-level middleware is more limited in what it can modify, relative - /// to Route or Application level middleware, in that Scope-level middleware - /// can not modify ServiceResponse. + /// Registers middleware, in the form of a closure, that runs during inbound processing in the + /// request life-cycle (request -> response), modifying request as necessary, across all + /// requests managed by the *Scope*. /// + /// # Examples /// ``` /// use actix_service::Service; /// use actix_web::{web, App}; @@ -369,21 +368,22 @@ where /// .route("/index.html", web::get().to(index))); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future>, + R: Future, Error>>, { Scope { endpoint: apply_fn_factory(self.endpoint, mw), @@ -394,6 +394,7 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, + _phantom: PhantomData, } } } From d35b7644dccacdc851cd7c52a7bde92a2cd4e86a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 7 Dec 2021 17:23:34 +0000 Subject: [PATCH 151/861] add connection level data container (#2491) --- CHANGES.md | 2 ++ actix-http/CHANGES.md | 3 ++ actix-http/src/extensions.rs | 37 ++++----------------- actix-http/src/h1/dispatcher.rs | 56 +++++++++++++++++--------------- actix-http/src/h1/service.rs | 12 ++----- actix-http/src/h2/dispatcher.rs | 13 ++++---- actix-http/src/h2/service.rs | 13 +++++--- actix-http/src/lib.rs | 10 +----- actix-http/src/request.rs | 30 ++++++++++++++++- actix-http/src/service.rs | 18 ++++------ actix-http/tests/test_openssl.rs | 4 +-- actix-http/tests/test_server.rs | 4 +-- examples/on_connect.rs | 25 ++++++++++---- src/app_service.rs | 5 ++- src/request.rs | 17 ++++++++++ src/server.rs | 4 +-- src/service.rs | 18 ++++++---- src/test.rs | 6 ++-- 18 files changed, 152 insertions(+), 125 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2adf54d3d..2ef1478dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -27,6 +28,7 @@ [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 +[#2491]: https://github.com/actix/actix-web/pull/2491 [#2492]: https://github.com/actix/actix-web/pull/2492 [#2493]: https://github.com/actix/actix-web/pull/2493 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1a59b233a..f435784d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -14,6 +14,8 @@ * `header::QualityItem::{max, min}`. [#2486] * `header::Quality::{MAX, MIN}`. [#2486] * `impl Display` for `header::Quality`. [#2486] +* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +* `Request::take_conn_data()`. [#2491] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -39,6 +41,7 @@ [#1920]: https://github.com/actix/actix-web/pull/1920 [#2486]: https://github.com/actix/actix-web/pull/2486 [#2488]: https://github.com/actix/actix-web/pull/2488 +[#2491]: https://github.com/actix/actix-web/pull/2491 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 5fdcefd6d..164919d87 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,6 +1,6 @@ use std::{ any::{Any, TypeId}, - fmt, mem, + fmt, }; use ahash::AHashMap; @@ -10,8 +10,7 @@ use ahash::AHashMap; /// 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. + /// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys. map: AHashMap>, } @@ -123,11 +122,6 @@ impl Extensions { pub fn extend(&mut self, other: Extensions) { self.map.extend(other.map); } - - /// Sets (or overrides) items from `other` into this map. - pub(crate) fn drain_from(&mut self, other: &mut Self) { - self.map.extend(mem::take(&mut other.map)); - } } impl fmt::Debug for Extensions { @@ -179,6 +173,8 @@ mod tests { #[test] fn test_integers() { + static A: u32 = 8; + let mut map = Extensions::new(); map.insert::(8); @@ -191,6 +187,7 @@ mod tests { map.insert::(32); map.insert::(64); map.insert::(128); + map.insert::<&'static u32>(&A); assert!(map.get::().is_some()); assert!(map.get::().is_some()); assert!(map.get::().is_some()); @@ -201,6 +198,7 @@ mod tests { assert!(map.get::().is_some()); assert!(map.get::().is_some()); assert!(map.get::().is_some()); + assert!(map.get::<&'static u32>().is_some()); } #[test] @@ -279,27 +277,4 @@ mod tests { assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get_mut(), Some(&mut 20u8)); } - - #[test] - fn test_drain_from() { - let mut ext = Extensions::new(); - ext.insert(2isize); - - let mut more_ext = Extensions::new(); - - more_ext.insert(5isize); - more_ext.insert(5usize); - - assert_eq!(ext.get::(), Some(&2isize)); - assert_eq!(ext.get::(), None); - assert_eq!(more_ext.get::(), Some(&5isize)); - assert_eq!(more_ext.get::(), Some(&5usize)); - - ext.drain_from(&mut more_ext); - - assert_eq!(ext.get::(), Some(&5isize)); - assert_eq!(ext.get::(), Some(&5usize)); - assert_eq!(more_ext.get::(), None); - assert_eq!(more_ext.get::(), None); - } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3c36e7367..b11054307 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -22,7 +22,7 @@ use crate::{ config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - OnConnectData, Request, Response, StatusCode, + Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -100,9 +100,9 @@ where U::Error: fmt::Display, { flow: Rc>, - on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, + conn_data: Option>, error: Option, #[pin] @@ -179,10 +179,10 @@ where /// Create HTTP/1 dispatcher. pub(crate) fn new( io: T, - config: ServiceConfig, flow: Rc>, - on_connect_data: OnConnectData, + config: ServiceConfig, peer_addr: Option, + conn_data: OnConnectData, ) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE @@ -198,20 +198,23 @@ where Dispatcher { inner: DispatcherState::Normal(InnerDispatcher { - read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - payload: None, - state: State::None, - error: None, - messages: VecDeque::new(), - io: Some(io), - codec: Codec::new(config), flow, - on_connect_data, flags, peer_addr, + conn_data: conn_data.0.map(Rc::new), + error: None, + + state: State::None, + payload: None, + messages: VecDeque::new(), + ka_expire, ka_timer, + + io: Some(io), + read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + codec: Codec::new(config), }), #[cfg(test)] @@ -593,8 +596,7 @@ where Message::Item(mut req) => { req.head_mut().peer_addr = *this.peer_addr; - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + req.conn_data = this.conn_data.as_ref().map(Rc::clone); match this.codec.message_type() { // Request is upgradable. add upgrade message and break. @@ -1100,10 +1102,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - ServiceConfig::default(), services, - OnConnectData::default(), + ServiceConfig::default(), None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1140,10 +1142,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1194,10 +1196,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); actix_rt::pin!(h1); @@ -1244,10 +1246,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( @@ -1316,10 +1318,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( @@ -1393,10 +1395,10 @@ mod tests { let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( buf.clone(), - cfg, services, - OnConnectData::default(), + cfg, None, + OnConnectData::default(), ); buf.extend_read_buf( diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 70e83901c..fd9635690 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -365,15 +365,7 @@ where } fn call(&self, (io, addr): (T, Option)) -> Self::Future { - let on_connect_data = - OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); - - Dispatcher::new( - io, - self.cfg.clone(), - self.flow.clone(), - on_connect_data, - addr, - ) + let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); + Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 6d2f4579a..22eab6c28 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -27,7 +27,7 @@ use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, service::HttpFlow, - OnConnectData, Payload, Request, Response, ResponseHead, + Extensions, OnConnectData, Payload, Request, Response, ResponseHead, }; const CHUNK_SIZE: usize = 16_384; @@ -37,7 +37,7 @@ pin_project! { pub struct Dispatcher { flow: Rc>, connection: Connection, - on_connect_data: OnConnectData, + conn_data: Option>, config: ServiceConfig, peer_addr: Option, ping_pong: Option, @@ -50,11 +50,11 @@ where T: AsyncRead + AsyncWrite + Unpin, { pub(crate) fn new( - flow: Rc>, mut conn: Connection, - on_connect_data: OnConnectData, + flow: Rc>, config: ServiceConfig, peer_addr: Option, + conn_data: OnConnectData, timer: Option>>, ) -> Self { let ping_pong = config.keep_alive().map(|dur| H2PingPong { @@ -74,7 +74,7 @@ where config, peer_addr, connection: conn, - on_connect_data, + conn_data: conn_data.0.map(Rc::new), ping_pong, _phantom: PhantomData, } @@ -119,8 +119,7 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; - // merge on_connect_ext data into request extensions - this.on_connect_data.merge_into(&mut req); + req.conn_data = this.conn_data.as_ref().map(Rc::clone); let fut = this.flow.service.call(req); let config = this.config.clone(); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 8a9061b94..aa2a6cc69 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,7 +1,7 @@ use std::{ future::Future, marker::PhantomData, - net, + mem, net, pin::Pin, rc::Rc, task::{Context, Poll}, @@ -339,21 +339,24 @@ where ref mut srv, ref mut config, ref peer_addr, - ref mut on_connect_data, + ref mut conn_data, ref mut handshake, ) => match ready!(Pin::new(handshake).poll(cx)) { Ok((conn, timer)) => { - let on_connect_data = std::mem::take(on_connect_data); + let on_connect_data = mem::take(conn_data); + self.state = State::Incoming(Dispatcher::new( - srv.take().unwrap(), conn, - on_connect_data, + srv.take().unwrap(), config.take().unwrap(), *peer_addr, + on_connect_data, timer, )); + self.poll(cx) } + Err(err) => { trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index aeba3da36..89ee139c0 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -92,19 +92,11 @@ impl OnConnectData { on_connect_ext: Option<&ConnectCallback>, ) -> Self { let ext = on_connect_ext.map(|handler| { - let mut extensions = Extensions::new(); + let mut extensions = Extensions::default(); handler(io, &mut extensions); extensions }); Self(ext) } - - /// Merge self into given request's extensions. - #[inline] - pub(crate) fn merge_into(&mut self, req: &mut Request) { - if let Some(ref mut ext) = self.0 { - req.head.extensions.get_mut().drain_from(ext); - } - } } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 401e9745c..78c0527b5 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -2,7 +2,9 @@ use std::{ cell::{Ref, RefMut}, - fmt, net, str, + fmt, net, + rc::Rc, + str, }; use http::{header, Method, Uri, Version}; @@ -19,6 +21,7 @@ use crate::{ pub struct Request

{ pub(crate) payload: Payload

, pub(crate) head: Message, + pub(crate) conn_data: Option>, } impl

HttpMessage for Request

{ @@ -51,6 +54,7 @@ impl From> for Request { Request { head, payload: Payload::None, + conn_data: None, } } } @@ -61,6 +65,7 @@ impl Request { Request { head: Message::new(), payload: Payload::None, + conn_data: None, } } } @@ -71,16 +76,19 @@ impl

Request

{ Request { payload, head: Message::new(), + conn_data: None, } } /// Create new Request instance pub fn replace_payload(self, payload: Payload) -> (Request, Payload

) { let pl = self.payload; + ( Request { payload, head: self.head, + conn_data: self.conn_data, }, pl, ) @@ -170,6 +178,26 @@ impl

Request

{ pub fn peer_addr(&self) -> Option { self.head().peer_addr } + + /// Returns a reference a piece of connection data set in an [on-connect] callback. + /// + /// ```ignore + /// let opt_t = req.conn_data::(); + /// ``` + /// + /// [on-connect]: crate::HttpServiceBuilder::on_connect_ext + pub fn conn_data(&self) -> Option<&T> { + self.conn_data + .as_deref() + .and_then(|container| container.get::()) + } + + /// Returns the connection data container if an [on-connect] callback was registered. + /// + /// [on-connect]: crate::HttpServiceBuilder::on_connect_ext + pub fn take_conn_data(&mut self) -> Option> { + self.conn_data.take() + } } impl

fmt::Debug for Request

{ diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 7af34ba05..cba4c1756 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -507,8 +507,7 @@ where &self, (io, proto, peer_addr): (T, Protocol, Option), ) -> Self::Future { - let on_connect_data = - OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); + let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); match proto { Protocol::Http2 => HttpServiceHandlerResponse { @@ -517,7 +516,7 @@ where h2::handshake_with_timeout(io, &self.cfg), self.cfg.clone(), self.flow.clone(), - on_connect_data, + conn_data, peer_addr, )), }, @@ -527,10 +526,10 @@ where state: State::H1 { dispatcher: h1::Dispatcher::new( io, - self.cfg.clone(), self.flow.clone(), - on_connect_data, + self.cfg.clone(), peer_addr, + conn_data, ), }, }, @@ -627,17 +626,12 @@ where StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { - let (_, config, flow, on_connect_data, peer_addr) = + let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2 { dispatcher: h2::Dispatcher::new( - flow, - conn, - on_connect_data, - config, - peer_addr, - timer, + conn, flow, config, peer_addr, conn_data, timer, ), }); self.poll(cx) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 6f68cc04d..8ba41b4bd 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -8,7 +8,7 @@ use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, header::{self, HeaderValue}, - Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version, + Error, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -430,7 +430,7 @@ async fn test_h2_on_connect() { data.insert(20isize); }) .h2(|req: Request| { - assert!(req.extensions().contains::()); + assert!(req.conn_data::().is_some()); ok::<_, Infallible>(Response::ok()) }) .openssl(tls_config()) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e6733b29b..b7fde877f 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -7,7 +7,7 @@ use std::{ use actix_http::{ body::{self, BodyStream, BoxBody, SizedStream}, - header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode, + header, Error, HttpService, KeepAlive, Request, Response, StatusCode, }; use actix_http_test::test_server; use actix_rt::time::sleep; @@ -748,7 +748,7 @@ async fn test_h1_on_connect() { data.insert(20isize); }) .h1(|req: Request| { - assert!(req.extensions().contains::()); + assert!(req.conn_data::().is_some()); ok::<_, Infallible>(Response::ok()) }) .tcp() diff --git a/examples/on_connect.rs b/examples/on_connect.rs index 9709835e6..d76e9ce56 100644 --- a/examples/on_connect.rs +++ b/examples/on_connect.rs @@ -6,7 +6,10 @@ use std::{any::Any, io, net::SocketAddr}; -use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; +use actix_web::{ + dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer, + Responder, +}; #[allow(dead_code)] #[derive(Debug, Clone)] @@ -16,11 +19,16 @@ struct ConnectionInfo { ttl: Option, } -async fn route_whoami(conn_info: web::ReqData) -> String { - format!( - "Here is some info about your connection:\n\n{:#?}", - conn_info - ) +async fn route_whoami(req: HttpRequest) -> impl Responder { + match req.conn_data::() { + Some(info) => HttpResponse::Ok().body(format!( + "Here is some info about your connection:\n\n{:#?}", + info + )), + None => { + HttpResponse::InternalServerError().body("Missing expected request extension data") + } + } } fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { @@ -39,9 +47,12 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + let bind = ("127.0.0.1", 8080); + log::info!("staring server at http://{}:{}", &bind.0, &bind.1); + HttpServer::new(|| App::new().default_service(web::to(route_whoami))) .on_connect(get_conn_info) - .bind(("127.0.0.1", 8080))? + .bind(bind)? .workers(1) .run() .await diff --git a/src/app_service.rs b/src/app_service.rs index bca8f2629..5dfc3b5ae 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -197,7 +197,8 @@ where actix_service::forward_ready!(service); - fn call(&self, req: Request) -> Self::Future { + fn call(&self, mut req: Request) -> Self::Future { + let conn_data = req.take_conn_data(); let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.app_state.pool().pop() { @@ -205,6 +206,7 @@ where inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; + inner.conn_data = conn_data; req } else { HttpRequest::new( @@ -212,6 +214,7 @@ where head, self.app_state.clone(), self.app_data.clone(), + conn_data, ) }; self.service.call(ServiceRequest::new(req, payload)) diff --git a/src/request.rs b/src/request.rs index f04d47c6f..b7f4f3510 100644 --- a/src/request.rs +++ b/src/request.rs @@ -37,6 +37,7 @@ pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, pub(crate) app_data: SmallVec<[Rc; 4]>, + pub(crate) conn_data: Option>, app_state: Rc, } @@ -47,6 +48,7 @@ impl HttpRequest { head: Message, app_state: Rc, app_data: Rc, + conn_data: Option>, ) -> HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); @@ -57,6 +59,7 @@ impl HttpRequest { path, app_state, app_data: data, + conn_data, }), } } @@ -165,6 +168,20 @@ impl HttpRequest { self.head().extensions_mut() } + /// Returns a reference a piece of connection data set in an [on-connect] callback. + /// + /// ```ignore + /// let opt_t = req.conn_data::(); + /// ``` + /// + /// [on-connect]: crate::HttpServiceBuilder::on_connect_ext + pub fn conn_data(&self) -> Option<&T> { + self.inner + .conn_data + .as_deref() + .and_then(|container| container.get::()) + } + /// Generates URL for a named resource. /// /// This substitutes in sequence all URL parameters that appear in the resource itself and in diff --git a/src/server.rs b/src/server.rs index 3db849410..b2ff423f1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -101,9 +101,9 @@ where /// Sets function that will be called once before each connection is handled. /// It will receive a `&std::any::Any`, which contains underlying connection type and an - /// [Extensions] container so that request-local data can be passed to middleware and handlers. + /// [Extensions] container so that connection data can be accessed in middleware and handlers. /// - /// For example: + /// # Connection Types /// - `actix_tls::accept::openssl::TlsStream` when using openssl. /// - `actix_tls::accept::rustls::TlsStream` when using rustls. /// - `actix_web::rt::net::TcpStream` when no encryption is used. diff --git a/src/service.rs b/src/service.rs index 4185d6018..d56752f13 100644 --- a/src/service.rs +++ b/src/service.rs @@ -172,12 +172,10 @@ impl ServiceRequest { self.head().uri.path() } - /// The query string in the URL. - /// - /// E.g., id=10 + /// Counterpart to [`HttpRequest::query_string`](super::HttpRequest::query_string()). #[inline] pub fn query_string(&self) -> &str { - self.uri().query().unwrap_or_default() + self.req.query_string() } /// Peer socket address. @@ -241,6 +239,7 @@ impl ServiceRequest { } /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). + #[inline] pub fn app_data(&self) -> Option<&T> { for container in self.req.inner.app_data.iter().rev() { if let Some(data) = container.get::() { @@ -251,6 +250,12 @@ impl ServiceRequest { None } + /// Counterpart to [`HttpRequest::conn_data`](super::HttpRequest::conn_data()). + #[inline] + pub fn conn_data(&self) -> Option<&T> { + self.req.conn_data() + } + #[cfg(feature = "cookies")] pub fn cookies(&self) -> Result>>, CookieParseError> { self.req.cookies() @@ -263,6 +268,7 @@ impl ServiceRequest { } /// Set request payload. + #[inline] pub fn set_payload(&mut self, payload: Payload) { self.payload = payload; } @@ -280,6 +286,7 @@ impl ServiceRequest { } impl Resource for ServiceRequest { + #[inline] fn resource_path(&mut self) -> &mut Path { self.match_info_mut() } @@ -404,12 +411,11 @@ impl ServiceResponse { } /// Extract response body + #[inline] pub fn into_body(self) -> B { self.response.into_body() } -} -impl ServiceResponse { /// Set a new body #[inline] pub fn map_body(self, f: F) -> ServiceResponse diff --git a/src/test.rs b/src/test.rs index 07d2d16b6..bff9c62dc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -581,7 +581,7 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); ServiceRequest::new( - HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)), + HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None), payload, ) } @@ -599,7 +599,7 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)) + HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None) } /// Complete request creation and generate `HttpRequest` and `Payload` instances @@ -610,7 +610,7 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)); + let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None); (req, payload) } From e49e559f47dd3642120dcccee5efaa94cb690e4d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 8 Dec 2021 05:43:50 +0000 Subject: [PATCH 152/861] fix some docs --- actix-http/src/h1/dispatcher.rs | 71 ++++++++++++++---------------- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/header/as_name.rs | 2 +- actix-http/src/header/into_pair.rs | 2 +- actix-http/src/response_builder.rs | 6 ++- src/error/mod.rs | 2 +- src/request.rs | 2 +- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index b11054307..d2410be1e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -27,6 +27,7 @@ use crate::{ use super::{ codec::Codec, + decoder::MAX_BUFFER_SIZE, payload::{Payload, PayloadSender, PayloadStatus}, Message, MessageType, }; @@ -793,7 +794,6 @@ where /// Returns true when io stream can be disconnected after write to it. /// /// It covers these conditions: - /// /// - `std::io::ErrorKind::ConnectionReset` after partial read. /// - all data read done. #[inline(always)] @@ -813,46 +813,39 @@ where loop { // 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 stream is still scheduled - to be waked up. so force wake up dispatcher just in case. + if this.read_buf.len() >= MAX_BUFFER_SIZE { + // At this point it's not known IO stream is still scheduled to be waked up so + // force wake up dispatcher just in case. + // + // Reason: + // AsyncRead mostly would only have guarantee wake up when the poll_read + // return Poll::Pending. + // + // Case: + // When read_buf is beyond max buffer size the early return could be successfully + // be parsed as a new Request. This case would not generate ParseError::TooLarge and + // at this point IO stream is not fully read to Pending and would result in + // dispatcher stuck until timeout (KA) + // + // Note: + // This is a perf choice to reduce branch on ::decode. + // + // A Request head too large to parse is only checked on + // `httparse::Status::Partial` condition. - Reason: - AsyncRead mostly would only have guarantee wake up - when the poll_read return Poll::Pending. - - Case: - When read_buf is beyond max buffer size the early return - could be successfully be parsed as a new Request. - This case would not generate ParseError::TooLarge - and at this point IO stream is not fully read to Pending - and would result in dispatcher stuck until timeout (KA) - - Note: - This is a perf choice to reduce branch on - ::decode. - - A Request head too large to parse is only checked on - httparse::Status::Partial condition. - */ if this.payload.is_none() { - /* - When dispatcher has a payload the responsibility of - wake up it would be shift to h1::payload::Payload. - - Reason: - Self wake up when there is payload would waste poll - and/or result in over read. - - Case: - When payload is (partial) dropped by user there is - no need to do read anymore. - At this case read_buf could always remain beyond - MAX_BUFFER_SIZE and self wake up would be busy poll - dispatcher and waste resource. - - */ + // When dispatcher has a payload the responsibility of wake up it would be shift + // to h1::payload::Payload. + // + // Reason: + // Self wake up when there is payload would waste poll and/or result in + // over read. + // + // Case: + // When payload is (partial) dropped by user there is no need to do + // read anymore. At this case read_buf could always remain beyond + // MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and + // waste resources. cx.waker().wake_by_ref(); } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 22eab6c28..55f71122b 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -109,7 +109,7 @@ where Poll::Ready(Some((req, tx))) => { let (parts, body) = req.into_parts(); let pl = crate::h2::Payload::new(body); - let pl = Payload::::H2(pl); + let pl = Payload::H2(pl); let mut req = Request::with_payload(pl); let head = req.head_mut(); diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index 04d32c41d..17d007f2f 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -6,7 +6,7 @@ use http::header::{HeaderName, InvalidHeaderName}; /// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`]. /// -/// [`HeaderValue`]: crate::http::HeaderValue +/// [`HeaderValue`]: super::HeaderValue pub trait AsHeaderName: Sealed {} pub struct Seal; diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs index 472700548..b4250e06e 100644 --- a/actix-http/src/header/into_pair.rs +++ b/actix-http/src/header/into_pair.rs @@ -12,7 +12,7 @@ use super::{Header, IntoHeaderValue}; /// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for /// insertion into a [`HeaderMap`]. /// -/// [`HeaderMap`]: crate::http::HeaderMap +/// [`HeaderMap`]: super::HeaderMap pub trait IntoHeaderPair: Sized { type Error: Into; diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index f11f89219..dfc2612fb 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -47,7 +47,8 @@ impl ResponseBuilder { /// Create response builder /// /// # Examples - // /// use actix_http::{Response, ResponseBuilder, StatusCode};, / `` + /// ``` + /// use actix_http::{Response, ResponseBuilder, StatusCode}; /// let res: Response<_> = ResponseBuilder::default().finish(); /// assert_eq!(res.status(), StatusCode::OK); /// ``` @@ -62,7 +63,8 @@ impl ResponseBuilder { /// Set HTTP status code of this response. /// /// # Examples - // /// use actix_http::{ResponseBuilder, StatusCode};, / `` + /// ``` + /// use actix_http::{ResponseBuilder, StatusCode}; /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// ``` diff --git a/src/error/mod.rs b/src/error/mod.rs index 90c2c9a61..48f71618c 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -2,7 +2,7 @@ /// This is meant to be a glob import of the whole error module, but rustdoc can't handle /// shadowing `Error` type, so it is expanded manually. -/// See https://github.com/rust-lang/rust/issues/83375 +/// See pub use actix_http::error::{ BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, }; diff --git a/src/request.rs b/src/request.rs index b7f4f3510..d99849eef 100644 --- a/src/request.rs +++ b/src/request.rs @@ -174,7 +174,7 @@ impl HttpRequest { /// let opt_t = req.conn_data::(); /// ``` /// - /// [on-connect]: crate::HttpServiceBuilder::on_connect_ext + /// [on-connect]: crate::HttpServer::on_connect pub fn conn_data(&self) -> Option<&T> { self.inner .conn_data From 406f69409550c8fc59dd4bde7be8d81f6fb65552 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 8 Dec 2021 06:01:11 +0000 Subject: [PATCH 153/861] standardize rustfmt max_width --- actix-files/src/files.rs | 2 +- actix-http/benches/status-line.rs | 12 +--- actix-http/benches/uninit-headers.rs | 9 +-- actix-http/examples/echo.rs | 5 +- actix-http/examples/echo2.rs | 3 +- actix-http/examples/hello-world.rs | 5 +- actix-http/examples/ws.rs | 5 +- actix-http/rustfmt.toml | 5 -- actix-http/src/body/body_stream.rs | 3 +- actix-http/src/body/boxed.rs | 4 +- actix-http/src/body/utils.rs | 5 +- actix-http/src/builder.rs | 3 +- actix-http/src/encoding/decoder.rs | 17 ++--- actix-http/src/encoding/encoder.rs | 6 +- actix-http/src/error.rs | 3 +- actix-http/src/h1/chunked.rs | 10 +-- actix-http/src/h1/decoder.rs | 9 +-- actix-http/src/h1/dispatcher.rs | 70 +++++++------------- actix-http/src/h1/encoder.rs | 25 ++----- actix-http/src/h1/payload.rs | 5 +- actix-http/src/h1/service.rs | 12 ++-- actix-http/src/h1/utils.rs | 21 +++--- actix-http/src/h2/dispatcher.rs | 9 +-- actix-http/src/h2/mod.rs | 5 +- actix-http/src/h2/service.rs | 6 +- actix-http/src/header/map.rs | 11 ++- actix-http/src/header/mod.rs | 32 +++++---- actix-http/src/header/shared/extended.rs | 15 ++--- actix-http/src/header/shared/http_date.rs | 3 +- actix-http/src/header/shared/quality_item.rs | 3 +- actix-http/src/lib.rs | 5 +- actix-http/src/payload.rs | 5 +- actix-http/src/response.rs | 4 +- actix-http/src/service.rs | 15 ++--- actix-http/src/ws/codec.rs | 12 +--- actix-http/src/ws/dispatcher.rs | 13 ++-- actix-http/src/ws/frame.rs | 11 +-- actix-http/src/ws/mask.rs | 8 +-- actix-http/src/ws/mod.rs | 4 +- actix-http/tests/test_client.rs | 4 +- actix-http/tests/test_openssl.rs | 25 +++---- actix-http/tests/test_rustls.rs | 18 ++--- actix-http/tests/test_server.rs | 52 ++++++--------- actix-http/tests/test_ws.rs | 9 ++- actix-router/src/resource.rs | 6 +- src/error/internal.rs | 2 +- src/error/mod.rs | 9 +-- src/response/builder.rs | 2 + 48 files changed, 192 insertions(+), 335 deletions(-) delete mode 100644 actix-http/rustfmt.toml diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 06909bf08..d1dd6739d 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -262,9 +262,9 @@ impl Files { self } + /// See [`Files::method_guard`]. #[doc(hidden)] #[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")] - /// See [`Files::method_guard`]. pub fn use_guards(self, guard: G) -> Self { self.method_guard(guard) } diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs index f62d18ed8..9fe099478 100644 --- a/actix-http/benches/status-line.rs +++ b/actix-http/benches/status-line.rs @@ -189,11 +189,7 @@ mod _original { n /= 100; curr -= 2; unsafe { - ptr::copy_nonoverlapping( - lut_ptr.offset(d1 as isize), - buf_ptr.offset(curr), - 2, - ); + ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); } // decode last 1 or 2 chars @@ -206,11 +202,7 @@ mod _original { let d1 = n << 1; curr -= 2; unsafe { - ptr::copy_nonoverlapping( - lut_ptr.offset(d1 as isize), - buf_ptr.offset(curr), - 2, - ); + ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); } } diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 53a2528ab..5dfd3bc11 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -54,15 +54,10 @@ const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex { value: (0, 0), }; -const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = - [EMPTY_HEADER_INDEX; MAX_HEADERS]; +const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS]; impl HeaderIndex { - fn record( - bytes: &[u8], - headers: &[httparse::Header<'_>], - indices: &mut [HeaderIndex], - ) { + fn record(bytes: &[u8], headers: &[httparse::Header<'_>], indices: &mut [HeaderIndex]) { let bytes_ptr = bytes.as_ptr() as usize; for (header, indices) in headers.iter().zip(indices.iter_mut()) { let name_start = header.name.as_ptr() as usize - bytes_ptr; diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 5ff2bcc89..22f553f38 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -25,10 +25,7 @@ async fn main() -> io::Result<()> { Ok::<_, Error>( Response::build(StatusCode::OK) - .insert_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 487b8d8d1..e3b915e05 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,8 +1,7 @@ use std::io; use actix_http::{ - body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, - StatusCode, + body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode, }; use actix_server::Server; use bytes::BytesMut; diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 3678774b8..0a46a89f9 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -17,10 +17,7 @@ async fn main() -> io::Result<()> { log::info!("{:?}", req); let mut res = Response::build(StatusCode::OK); - res.insert_header(( - "x-head", - HeaderValue::from_static("dummy value!"), - )); + res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); Ok::<_, Infallible>(res.body("Hello world!")) }) diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index b6be4d2f1..d70e43314 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -60,10 +60,7 @@ impl Heartbeat { impl Stream for Heartbeat { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { log::trace!("poll"); ready!(self.as_mut().interval.poll_tick(cx)); diff --git a/actix-http/rustfmt.toml b/actix-http/rustfmt.toml deleted file mode 100644 index 5fcaaca0f..000000000 --- a/actix-http/rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -max_width = 89 -reorder_imports = true -#wrap_comments = true -#fn_args_density = "Compressed" -#use_small_heuristics = false diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 1da7a848a..232d01590 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -165,8 +165,7 @@ mod tests { #[actix_rt::test] async fn stream_delayed_error() { - let body = - BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)])); + let body = BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)])); assert!(matches!(to_bytes(body).await, Err(StreamErr))); pin_project! { diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index 9442bd1df..c39da10c0 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -24,9 +24,7 @@ impl BoxBody { } /// Returns a mutable pinned reference to the inner message body type. - pub fn as_pin_mut( - &mut self, - ) -> Pin<&mut (dyn MessageBody>)> { + pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody>)> { self.0.as_mut() } } diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs index a421ffd76..194af47f8 100644 --- a/actix-http/src/body/utils.rs +++ b/actix-http/src/body/utils.rs @@ -68,9 +68,8 @@ mod test { let bytes = to_bytes(body).await.unwrap(); assert_eq!(bytes, b"123"[..]); - let stream = - stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")]) - .map(Ok::<_, Error>); + let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")]) + .map(Ok::<_, Error>); let body = BodyStream::new(stream); let bytes = to_bytes(body).await.unwrap(); assert_eq!(bytes, b"123abc"[..]); diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index ca821f1d9..1b5da20b6 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -214,8 +214,7 @@ where self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()) - .on_connect_ext(self.on_connect_ext) + H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index afe4c6e13..a46e330c9 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -44,17 +44,17 @@ where pub fn new(stream: S, encoding: ContentEncoding) -> Decoder { let decoder = match encoding { #[cfg(feature = "compress-brotli")] - ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( - BrotliDecoder::new(Writer::new()), - ))), + ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(BrotliDecoder::new( + Writer::new(), + )))), #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ZlibDecoder::new(Writer::new()), ))), #[cfg(feature = "compress-gzip")] - ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( - GzDecoder::new(Writer::new()), - ))), + ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new( + Writer::new(), + )))), #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new( ZstdDecoder::new(Writer::new()).expect( @@ -93,10 +93,7 @@ where { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if let Some(ref mut fut) = self.fut { let (chunk, decoder) = diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 350e7f062..0886221cc 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -53,11 +53,7 @@ impl Encoder { } } - pub fn response( - encoding: ContentEncoding, - head: &mut ResponseHead, - body: B, - ) -> Self { + pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 231e90e57..a04867ae1 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -457,8 +457,7 @@ mod tests { #[test] fn test_payload_error() { - let err: PayloadError = - io::Error::new(io::ErrorKind::Other, "ParseError").into(); + let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into(); assert!(err.to_string().contains("ParseError")); let err = PayloadError::Incomplete(None); diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index e5b734fff..7d0532fcd 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -50,10 +50,7 @@ impl ChunkedState { } } - fn read_size( - rdr: &mut BytesMut, - size: &mut u64, - ) -> Poll> { + fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll> { let radix = 16; let rem = match byte!(rdr) { @@ -111,10 +108,7 @@ impl ChunkedState { _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions } } - fn read_size_lf( - rdr: &mut BytesMut, - size: u64, - ) -> Poll> { + fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll> { match byte!(rdr) { b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)), b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index a4db19669..eb142f844 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -74,8 +74,7 @@ pub(crate) trait MessageType: Sized { let headers = self.headers_mut(); for idx in raw_headers.iter() { - let name = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); + let name = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); // SAFETY: httparse already checks header value is only visible ASCII bytes // from_maybe_shared_unchecked contains debug assertions so they are omitted here @@ -605,8 +604,7 @@ mod tests { #[test] fn test_parse_body() { - let mut buf = - BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); @@ -622,8 +620,7 @@ mod tests { #[test] fn test_parse_body_crlf() { - let mut buf = - BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index d2410be1e..64bf83e03 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -260,10 +260,7 @@ where } } - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); @@ -273,10 +270,7 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::WriteZero, - "", - ))) + return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))) } Poll::Ready(n) => written += n, Poll::Pending => { @@ -419,15 +413,12 @@ where while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { match stream.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { - this.codec.encode( - Message::Chunk(Some(item)), - this.write_buf, - )?; + this.codec + .encode(Message::Chunk(Some(item)), this.write_buf)?; } Poll::Ready(None) => { - this.codec - .encode(Message::Chunk(None), this.write_buf)?; + this.codec.encode(Message::Chunk(None), this.write_buf)?; // payload stream finished. // set state to None and handle next message this.state.set(State::None); @@ -454,15 +445,12 @@ where while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { match stream.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { - this.codec.encode( - Message::Chunk(Some(item)), - this.write_buf, - )?; + this.codec + .encode(Message::Chunk(Some(item)), this.write_buf)?; } Poll::Ready(None) => { - this.codec - .encode(Message::Chunk(None), this.write_buf)?; + this.codec.encode(Message::Chunk(None), this.write_buf)?; // payload stream finished. // set state to None and handle next message this.state.set(State::None); @@ -568,9 +556,11 @@ where } }; } - _ => unreachable!( - "State must be set to ServiceCall or ExceptCall in handle_request" - ), + _ => { + unreachable!( + "State must be set to ServiceCall or ExceptCall in handle_request" + ) + } } } } @@ -604,8 +594,7 @@ where // everything remain in read buffer would be handed to // upgraded Request. MessageType::Stream if this.flow.upgrade.is_some() => { - this.messages - .push_back(DispatcherMessage::Upgrade(req)); + this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } @@ -620,8 +609,7 @@ where where the state can be collected and consumed. */ let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); + let (req1, _) = req.replace_payload(crate::Payload::H1(pl)); req = req1; *this.payload = Some(ps); } @@ -642,9 +630,7 @@ where if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - error!( - "Internal server error: unexpected payload chunk" - ); + error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -682,12 +668,11 @@ where payload.set_error(PayloadError::Overflow); } // Requests overflow buffer size should be responded with 431 - this.messages.push_back(DispatcherMessage::Error( - Response::with_body( + this.messages + .push_back(DispatcherMessage::Error(Response::with_body( StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, (), - ), - )); + ))); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); break; @@ -729,8 +714,7 @@ where None => { // conditionally go into shutdown timeout if this.flags.contains(Flags::SHUTDOWN) { - if let Some(deadline) = this.codec.config().client_disconnect_timer() - { + 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))); @@ -773,9 +757,7 @@ where this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); } // still have unfinished task. try to reset and register keep-alive. - } else if let Some(deadline) = - this.codec.config().keep_alive_expire() - { + } else if let Some(deadline) = this.codec.config().keep_alive_expire() { timer.as_mut().reset(deadline); let _ = timer.poll(cx); } @@ -1053,14 +1035,12 @@ mod tests { } fn ok_service( - ) -> impl Service, Error = Error> - { + ) -> impl Service, Error = Error> { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) } fn echo_path_service( - ) -> impl Service, Error = Error> - { + ) -> impl Service, Error = Error> { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>( @@ -1069,8 +1049,8 @@ mod tests { }) } - fn echo_payload_service( - ) -> impl Service, Error = Error> { + fn echo_payload_service() -> impl Service, Error = Error> + { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index fccd5da46..49bf5432d 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -103,9 +103,7 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"\r\n"); } } - BodySize::Sized(0) if camel_case => { - dst.put_slice(b"\r\nContent-Length: 0\r\n") - } + BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"), BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"), BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::None => dst.put_slice(b"\r\n"), @@ -307,11 +305,7 @@ impl MessageType for RequestHeadType { Version::HTTP_11 => "HTTP/1.1", Version::HTTP_2 => "HTTP/2.0", Version::HTTP_3 => "HTTP/3.0", - _ => - return Err(io::Error::new( - io::ErrorKind::Other, - "unsupported version" - )), + _ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")), } ) .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) @@ -568,8 +562,7 @@ mod tests { ConnectionType::Close, &ServiceConfig::default(), ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Connection: close\r\n")); @@ -583,8 +576,7 @@ mod tests { ConnectionType::KeepAlive, &ServiceConfig::default(), ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); assert!(data.contains("Transfer-Encoding: chunked\r\n")); assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Date: date\r\n")); @@ -605,8 +597,7 @@ mod tests { ConnectionType::KeepAlive, &ServiceConfig::default(), ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); assert!(data.contains("transfer-encoding: chunked\r\n")); assert!(data.contains("content-type: xml\r\n")); assert!(data.contains("content-type: plain/text\r\n")); @@ -639,8 +630,7 @@ mod tests { ConnectionType::Close, &ServiceConfig::default(), ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); assert!(data.contains("content-length: 0\r\n")); assert!(data.contains("connection: close\r\n")); assert!(data.contains("authorization: another authorization\r\n")); @@ -663,8 +653,7 @@ mod tests { ConnectionType::Upgrade, &ServiceConfig::default(), ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); assert!(!data.contains("content-length: 0\r\n")); assert!(!data.contains("transfer-encoding: chunked\r\n")); } diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index cc771f28a..f912e0ba3 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -227,10 +227,7 @@ impl Inner { self.len } - fn readany( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { + fn readany(&mut self, cx: &mut Context<'_>) -> Poll>> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < MAX_BUFFER_SIZE; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index fd9635690..c4e6e7714 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -266,8 +266,7 @@ where } } -impl ServiceFactory<(T, Option)> - for H1Service +impl ServiceFactory<(T, Option)> for H1Service where T: AsyncRead + AsyncWrite + Unpin + 'static, @@ -310,9 +309,9 @@ where let upgrade = match upgrade { Some(upgrade) => { - let upgrade = upgrade.await.map_err(|e| { - log::error!("Init http upgrade service error: {:?}", e) - })?; + let upgrade = upgrade + .await + .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -336,8 +335,7 @@ where /// `Service` implementation for HTTP/1 transport pub type H1ServiceHandler = HttpServiceHandler; -impl Service<(T, Option)> - for HttpServiceHandler +impl Service<(T, Option)> for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 905585a32..c8d79f0cd 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -70,15 +70,12 @@ where .unwrap() .is_write_buf_full() { - let next = - match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) { - Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)), - Poll::Ready(Some(Err(err))) => { - return Poll::Ready(Err(err.into())) - } - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - }; + let next = match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) { + Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)), + Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }; match next { Poll::Ready(item) => { @@ -88,9 +85,9 @@ where let _ = this.body.take(); } let framed = this.framed.as_mut().as_pin_mut().unwrap(); - framed.write(Message::Chunk(item)).map_err(|err| { - Error::new_send_response().with_cause(err) - })?; + framed + .write(Message::Chunk(item)) + .map_err(|err| Error::new_send_response().with_cause(err))?; } Poll::Pending => body_ready = false, } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 55f71122b..da2d612f1 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -160,16 +160,11 @@ where Poll::Ready(_) => { ping_pong.on_flight = false; - let dead_line = - this.config.keep_alive_expire().unwrap(); + let dead_line = this.config.keep_alive_expire().unwrap(); ping_pong.timer.as_mut().reset(dead_line); } Poll::Pending => { - return ping_pong - .timer - .as_mut() - .poll(cx) - .map(|_| Ok(())) + return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) } } } else { diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index 25d53403e..cbcb6d0fc 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -40,10 +40,7 @@ impl Payload { impl Stream for Payload { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); match ready!(Pin::new(&mut this.stream).poll_data(cx)) { diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index aa2a6cc69..f5821370a 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -10,8 +10,7 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; use actix_service::{ - fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, - ServiceFactoryExt as _, + fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _, }; use actix_utils::future::ready; use futures_core::{future::LocalBoxFuture, ready}; @@ -279,8 +278,7 @@ where } fn call(&self, (io, addr): (T, Option)) -> Self::Future { - let on_connect_data = - OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); + let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); H2ServiceHandlerResponse { state: State::Handshake( diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 7b18be991..12c8f9462 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -123,12 +123,11 @@ impl HeaderMap { let mut map = HeaderMap::with_capacity(capacity); map.append(first_name.clone(), first_value); - let (map, _) = - drain.fold((map, first_name), |(mut map, prev_name), (name, value)| { - let name = name.unwrap_or(prev_name); - map.append(name.clone(), value); - (map, name) - }); + let (map, _) = drain.fold((map, first_name), |(mut map, prev_name), (name, value)| { + let name = name.unwrap_or(prev_name); + map.append(name.clone(), value); + (map, name) + }); map } diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 381842e74..5fe76381b 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -11,22 +11,20 @@ pub use http::header::{ pub use http::header::{ ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, - ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, - ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE, - ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC, - AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING, - CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, - CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, - DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, - IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, - LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE, - PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, - REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, - SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, + ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, + ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, + ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, + CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, + CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, + DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, + IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, + ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, + PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, + SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER, - TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, - WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, - X_FRAME_OPTIONS, X_XSS_PROTECTION, + TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING, + WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, + X_XSS_PROTECTION, }; use crate::{error::ParseError, HttpMessage}; @@ -43,8 +41,8 @@ pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; pub use self::map::HeaderMap; pub use self::shared::{ - parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, - LanguageTag, Quality, QualityItem, + parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag, + Quality, QualityItem, }; pub use self::utils::{ fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 60f2d359e..1af9ca20e 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -63,9 +63,7 @@ pub struct ExtendedValue { /// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7 /// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3 /// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1 -pub fn parse_extended_value( - val: &str, -) -> Result { +pub fn parse_extended_value(val: &str) -> Result { // Break into three pieces separated by the single-quote character let mut parts = val.splitn(3, '\''); @@ -100,8 +98,7 @@ pub fn parse_extended_value( 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); + 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 { @@ -143,8 +140,8 @@ mod tests { 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', + 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 ); @@ -185,8 +182,8 @@ mod tests { 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', + 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!( diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 8dbdf4a62..228f6f00e 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -4,8 +4,7 @@ use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use crate::{ - config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, - helpers::MutWriter, + config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter, }; /// A timestamp with HTTP-style formatting and parsing. diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 9354915ad..c9eee7d9d 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -120,8 +120,7 @@ impl str::FromStr for QualityItem { } let q_value = q_val.parse::().map_err(|_| ParseError::Header)?; - let q_value = - Quality::try_from(q_value).map_err(|_| ParseError::Header)?; + let q_value = Quality::try_from(q_value).map_err(|_| ParseError::Header)?; quality = q_value; raw_item = val; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 89ee139c0..19c66d155 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -87,10 +87,7 @@ pub(crate) struct OnConnectData(Option); impl OnConnectData { /// Construct by calling the on-connect callback with the underlying transport I/O. - pub(crate) fn from_io( - io: &T, - on_connect_ext: Option<&ConnectCallback>, - ) -> Self { + pub(crate) fn from_io(io: &T, on_connect_ext: Option<&ConnectCallback>) -> Self { let ext = on_connect_ext.map(|handler| { let mut extensions = Extensions::default(); handler(io, &mut extensions); diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 54de6ed93..85bfc0b5a 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -56,10 +56,7 @@ where type Item = Result; #[inline] - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.get_mut() { Payload::None => Poll::Ready(None), Payload::H1(ref mut pl) => pl.readany(cx), diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ee7e38913..861cab2cb 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -231,9 +231,7 @@ impl Default for Response { } } -impl>, E: Into> From> - for Response -{ +impl>, E: Into> From> for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index cba4c1756..93168749d 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -161,11 +161,7 @@ where X::Error: Into>, X::InitError: fmt::Debug, - U: ServiceFactory< - (Request, Framed), - Config = (), - Response = (), - >, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Future: 'static, U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, @@ -381,9 +377,9 @@ where let upgrade = match upgrade { Some(upgrade) => { - let upgrade = upgrade.await.map_err(|e| { - log::error!("Init http upgrade service error: {:?}", e) - })?; + let upgrade = upgrade + .await + .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -626,8 +622,7 @@ where StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { - let (_, config, flow, conn_data, peer_addr) = - data.take().unwrap(); + let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2 { dispatcher: h2::Dispatcher::new( diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index d80613e5f..f5b755eec 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -224,9 +224,7 @@ impl Decoder for Codec { OpCode::Continue => { if self.flags.contains(Flags::CONTINUATION) { Ok(Some(Frame::Continuation(Item::Continue( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), + payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), )))) } else { Err(ProtocolError::ContinuationNotStarted) @@ -236,9 +234,7 @@ impl Decoder for Codec { if !self.flags.contains(Flags::CONTINUATION) { self.flags.insert(Flags::CONTINUATION); Ok(Some(Frame::Continuation(Item::FirstBinary( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), + payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), )))) } else { Err(ProtocolError::ContinuationStarted) @@ -248,9 +244,7 @@ impl Decoder for Codec { if !self.flags.contains(Flags::CONTINUATION) { self.flags.insert(Flags::CONTINUATION); Ok(Some(Frame::Continuation(Item::FirstText( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), + payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), )))) } else { Err(ProtocolError::ContinuationStarted) diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index a3f766e9c..f12ae1b1a 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -304,8 +304,7 @@ mod inner { let item = match this.framed.next_item(cx) { Poll::Ready(Some(Ok(el))) => el, Poll::Ready(Some(Err(err))) => { - *this.state = - State::FramedError(DispatcherError::Decoder(err)); + *this.state = State::FramedError(DispatcherError::Decoder(err)); return true; } Poll::Pending => return false, @@ -348,8 +347,7 @@ mod inner { match Pin::new(&mut this.rx).poll_next(cx) { Poll::Ready(Some(Ok(Message::Item(msg)))) => { if let Err(err) = this.framed.as_mut().write(msg) { - *this.state = - State::FramedError(DispatcherError::Encoder(err)); + *this.state = State::FramedError(DispatcherError::Encoder(err)); return true; } } @@ -371,8 +369,7 @@ mod inner { Poll::Ready(Ok(_)) => {} Poll::Ready(Err(err)) => { debug!("Error sending data: {:?}", err); - *this.state = - State::FramedError(DispatcherError::Encoder(err)); + *this.state = State::FramedError(DispatcherError::Encoder(err)); return true; } } @@ -432,9 +429,7 @@ mod inner { Poll::Ready(Ok(())) } } - State::FramedError(_) => { - Poll::Ready(Err(this.state.take_framed_error())) - } + State::FramedError(_) => Poll::Ready(Err(this.state.take_framed_error())), State::Stopping => Poll::Ready(Ok(())), }; } diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 46edf5d85..b58ef7362 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -16,8 +16,7 @@ impl Parser { src: &[u8], server: bool, max_size: usize, - ) -> Result)>, ProtocolError> - { + ) -> Result)>, ProtocolError> { let chunk_len = src.len(); let mut idx = 2; @@ -228,15 +227,11 @@ mod tests { payload: Bytes, } - fn is_none( - frm: &Result)>, ProtocolError>, - ) -> bool { + fn is_none(frm: &Result)>, ProtocolError>) -> bool { matches!(*frm, Ok(None)) } - fn extract( - frm: Result)>, ProtocolError>, - ) -> F { + fn extract(frm: Result)>, ProtocolError>) -> F { match frm { Ok(Some((finished, opcode, payload))) => F { finished, diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 11a6ddc32..20b4372a0 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -54,8 +54,8 @@ mod tests { let mask = [0x6d, 0xb6, 0xb2, 0x80]; let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, - 0x74, 0xf9, 0x12, 0x03, + 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, + 0x12, 0x03, ]; // Check masking with proper alignment. @@ -85,8 +85,8 @@ mod tests { fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, - 0x74, 0xf9, 0x12, 0x03, + 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, + 0x12, 0x03, ]; for data_len in 0..=unmasked.len() { diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cb1aa6730..c23d4edfc 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,9 +9,7 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::body::BoxBody; -use crate::{ - header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder, -}; +use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder}; mod codec; mod dispatcher; diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index acbdc8e83..a3adcdfd6 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,8 +1,6 @@ use std::convert::Infallible; -use actix_http::{ - body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode, -}; +use actix_http::{body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode}; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; use actix_utils::future; diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 8ba41b4bd..0c373b8b2 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -170,10 +170,11 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); - HttpService::build().h2(move |_| { - let mut builder = Response::build(StatusCode::OK); - for idx in 0..90 { - builder.insert_header( + HttpService::build() + .h2(move |_| { + let mut builder = Response::build(StatusCode::OK); + for idx in 0..90 { + 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 \ @@ -189,12 +190,13 @@ 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 ", )); - } - ok::<_, Infallible>(builder.body(data.clone())) - }) + } + ok::<_, Infallible>(builder.body(data.clone())) + }) .openssl(tls_config()) - .map_err(|_| ()) - }).await; + .map_err(|_| ()) + }) + .await; let response = srv.sget("/").send().await.unwrap(); assert!(response.status().is_success()); @@ -315,9 +317,8 @@ async fn test_h2_body_length() { let mut srv = test_server(move || { HttpService::build() .h2(|_| async { - let body = once(async { - Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) - }); + let body = + once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) }); Ok::<_, Infallible>( Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 1fc3bdf49..42ff0dba1 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -238,10 +238,11 @@ async fn test_h2_headers() { let mut srv = test_server(move || { let data = data.clone(); - HttpService::build().h2(move |_| { - let mut config = Response::build(StatusCode::OK); - for idx in 0..90 { - config.insert_header(( + HttpService::build() + .h2(move |_| { + let mut config = Response::build(StatusCode::OK); + for idx in 0..90 { + 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 \ @@ -257,11 +258,12 @@ 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 ", )); - } - ok::<_, Infallible>(config.body(data.clone())) - }) + } + ok::<_, Infallible>(config.body(data.clone())) + }) .rustls(tls_config()) - }).await; + }) + .await; let response = srv.sget("/").send().await.unwrap(); assert!(response.status().is_success()); diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index b7fde877f..1bb574fd6 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -154,9 +154,7 @@ async fn test_chunked_payload() { }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) .map(|req_size| { - Ok::<_, Error>( - Response::ok().set_body(format!("size={}", req_size)), - ) + Ok::<_, Error>(Response::ok().set_body(format!("size={}", req_size))) }) })) .tcp() @@ -165,8 +163,7 @@ async fn test_chunked_payload() { let returned_size = { let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); + let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); for chunk_size in chunk_sizes.iter() { let mut bytes = Vec::new(); @@ -293,8 +290,7 @@ async fn test_http1_keepalive_close() { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = - stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); @@ -338,8 +334,8 @@ async fn test_http10_keepalive() { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); + let _ = + stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); @@ -436,10 +432,11 @@ async fn test_h1_headers() { let mut srv = test_server(move || { let data = data.clone(); - HttpService::build().h1(move |_| { - let mut builder = Response::build(StatusCode::OK); - for idx in 0..90 { - builder.insert_header(( + HttpService::build() + .h1(move |_| { + let mut builder = Response::build(StatusCode::OK); + for idx in 0..90 { + 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 \ @@ -455,10 +452,12 @@ 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 ", )); - } - ok::<_, Infallible>(builder.body(data.clone())) - }).tcp() - }).await; + } + ok::<_, Infallible>(builder.body(data.clone())) + }) + .tcp() + }) + .await; let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); @@ -655,9 +654,7 @@ async fn test_h1_body_chunked_implicit() { HttpService::build() .h1(|_| { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, Infallible>( - Response::build(StatusCode::OK).body(BodyStream::new(body)), - ) + ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(body))) }) .tcp() }) @@ -776,10 +773,8 @@ async fn test_not_modified_spec_h1() { .h1(|req: Request| { let res: Response = match req.path() { // with no content-length - "/none" => { - Response::with_body(StatusCode::NOT_MODIFIED, body::None::new()) - .map_into_boxed_body() - } + "/none" => Response::with_body(StatusCode::NOT_MODIFIED, body::None::new()) + .map_into_boxed_body(), // with no content-length "/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234") @@ -787,10 +782,8 @@ async fn test_not_modified_spec_h1() { // with manual content-length header and specific None body "/cl-none" => { - let mut res = Response::with_body( - StatusCode::NOT_MODIFIED, - body::None::new(), - ); + let mut res = + Response::with_body(StatusCode::NOT_MODIFIED, body::None::new()); res.headers_mut() .insert(CL.clone(), header::HeaderValue::from_static("24")); res.map_into_boxed_body() @@ -798,8 +791,7 @@ async fn test_not_modified_spec_h1() { // with manual content-length header and ignore-able body "/cl-body" => { - let mut res = - Response::with_body(StatusCode::NOT_MODIFIED, "1234"); + let mut res = Response::with_body(StatusCode::NOT_MODIFIED, "1234"); res.headers_mut() .insert(CL.clone(), header::HeaderValue::from_static("4")); res.map_into_boxed_body() diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index c91382013..ed8c61fd6 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -56,8 +56,9 @@ impl From for Response { WsServiceError::Http(err) => err.into(), WsServiceError::Ws(err) => err.into(), WsServiceError::Io(_err) => unreachable!(), - WsServiceError::Dispatcher => Response::internal_server_error() - .set_body(BoxBody::new(format!("{}", err))), + WsServiceError::Dispatcher => { + Response::internal_server_error().set_body(BoxBody::new(format!("{}", err))) + } } } } @@ -97,9 +98,7 @@ where async fn service(msg: Frame) -> Result { let msg = match msg { Frame::Ping(msg) => Message::Pong(msg), - Frame::Text(text) => { - Message::Text(String::from_utf8_lossy(&text).into_owned().into()) - } + Frame::Text(text) => Message::Text(String::from_utf8_lossy(&text).into_owned().into()), Frame::Binary(bin) => Message::Binary(bin), Frame::Continuation(item) => Message::Continuation(item), Frame::Close(reason) => Message::Close(reason), diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index d5f738a05..fa77b1e7b 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -168,7 +168,7 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// extracted in the same way as non-tail dynamic segments. /// /// ## Examples -/// ```rust +/// ``` /// # use actix_router::{Path, ResourceDef}; /// let resource = ResourceDef::new("/blob/{tail}*"); /// assert!(resource.is_match("/blob/HEAD/Cargo.toml")); @@ -191,7 +191,7 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// expectations in the router using these definitions and cause runtime panics. /// /// ## Examples -/// ```rust +/// ``` /// # use actix_router::ResourceDef; /// let resource = ResourceDef::new(["/home", "/index"]); /// assert!(resource.is_match("/home")); @@ -206,7 +206,7 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// resource-path pairs that would not be compatible. /// /// ## Examples -/// ```rust +/// ``` /// # use actix_router::ResourceDef; /// assert!(!ResourceDef::new("/root").is_match("/root/")); /// assert!(!ResourceDef::new("/root/").is_match("/root")); diff --git a/src/error/internal.rs b/src/error/internal.rs index c766ba83e..b8e169018 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -128,7 +128,7 @@ macro_rules! error_helper { InternalError::new(err, StatusCode::$status).into() } } - } + }; } error_helper!(ErrorBadRequest, BAD_REQUEST); diff --git a/src/error/mod.rs b/src/error/mod.rs index 48f71618c..4877358a4 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,8 +1,9 @@ //! Error and Result module - -/// This is meant to be a glob import of the whole error module, but rustdoc can't handle -/// shadowing `Error` type, so it is expanded manually. -/// See +// This is meant to be a glob import of the whole error module except for `Error`. Rustdoc can't yet +// correctly resolve the conflicting `Error` type defined in this module, so these re-exports are +// expanded manually. +// +// See pub use actix_http::error::{ BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, }; diff --git a/src/response/builder.rs b/src/response/builder.rs index 50e23f81b..18a1c8a7f 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -109,6 +109,7 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::insert_header()`]. + #[doc(hidden)] #[deprecated( since = "4.0.0", note = "Replaced with `insert_header((key, value))`. Will be removed in v5." @@ -133,6 +134,7 @@ impl HttpResponseBuilder { } /// Replaced with [`Self::append_header()`]. + #[doc(hidden)] #[deprecated( since = "4.0.0", note = "Replaced with `append_header((key, value))`. Will be removed in v5." From 07f2fe385b1845eca1599904da9476487e7999f5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 8 Dec 2021 06:09:56 +0000 Subject: [PATCH 154/861] standardize crate level lints --- actix-files/src/lib.rs | 4 ++-- actix-http-test/src/lib.rs | 3 ++- actix-http/src/lib.rs | 3 ++- actix-multipart/src/lib.rs | 3 ++- actix-router/src/lib.rs | 1 + actix-test/src/lib.rs | 3 +++ actix-web-actors/src/lib.rs | 4 ++-- actix-web-codegen/src/lib.rs | 2 ++ actix-web-codegen/src/route.rs | 5 +---- awc/src/lib.rs | 3 ++- src/lib.rs | 1 + 11 files changed, 20 insertions(+), 12 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 3af5282f1..6408e02da 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -11,8 +11,8 @@ //! .service(Files::new("/static", ".").prefer_utf8(true)); //! ``` -#![deny(rust_2018_idioms)] -#![warn(missing_docs, missing_debug_implementations)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible, missing_docs, missing_debug_implementations)] use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index ff86e565a..e7e479ab2 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -1,6 +1,7 @@ //! Various helpers for Actix applications to use during testing. -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 19c66d155..60dc26f0f 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -14,7 +14,8 @@ //! [rustls]: https://crates.io/crates/rustls //! [trust-dns]: https://crates.io/crates/trust-dns -#![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![allow( clippy::type_complexity, clippy::too_many_arguments, diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 38a24e28f..3d536e08d 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -1,6 +1,7 @@ //! Multipart form support for Actix Web. -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![allow(clippy::borrow_interior_mutable_const)] mod error; diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 463e59e42..f616f7fc6 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -1,6 +1,7 @@ //! Resource path matching and router. #![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 7e493ce71..934b8f3aa 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -26,6 +26,9 @@ //! } //! ``` +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] + #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; #[cfg(feature = "rustls")] diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 7a4823d91..70c957020 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,7 +1,7 @@ //! Actix actors support for Actix Web. -#![deny(rust_2018_idioms)] -#![allow(clippy::borrow_interior_mutable_const)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] mod context; pub mod ws; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index cebf9e5fb..52cfc0d8f 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -57,6 +57,8 @@ //! [DELETE]: macro@delete #![recursion_limit = "512"] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] use proc_macro::TokenStream; use quote::quote; diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index eac1948a7..a4472efd2 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -1,7 +1,4 @@ -extern crate proc_macro; - -use std::collections::HashSet; -use std::convert::TryFrom; +use std::{collections::HashSet, convert::TryFrom}; use actix_router::ResourceDef; use proc_macro::TokenStream; diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 0cb6c7f4f..06fd33fac 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -95,7 +95,8 @@ //! # } //! ``` -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![allow( clippy::type_complexity, clippy::borrow_interior_mutable_const, diff --git a/src/lib.rs b/src/lib.rs index f6ec4082a..a44c9b3fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ //! * `secure-cookies` - secure cookies support #![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] From 7dc034f0fb70846d9bb3445a2414a142356892e1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 8 Dec 2021 22:58:50 +0000 Subject: [PATCH 155/861] Remove extensions from head (#2487) --- CHANGES.md | 6 +++++ actix-http/CHANGES.md | 3 +++ actix-http/examples/hello-world.rs | 16 ++++++++++--- actix-http/src/extensions.rs | 2 +- actix-http/src/message.rs | 17 +------------ actix-http/src/request.rs | 26 +++++++++++++------- src/app_service.rs | 3 +++ src/info.rs | 12 ++-------- src/request.rs | 38 ++++++++++++++++-------------- src/request_data.rs | 9 ++++--- src/service.rs | 2 +- src/test.rs | 27 ++++++++++++++++++--- 12 files changed, 96 insertions(+), 65 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2ef1478dc..365c89af9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +* `HttpRequest::{req_data,req_data_mut}`. [#2487] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -16,18 +17,23 @@ * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] * Remove `B` (body) type parameter on `App`. [#2493] * Add `B` (body) type parameter on `Scope`. [#2492] +* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] ### Fixed * Accept wildcard `*` items in `AcceptLanguage`. [#2480] * Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +### Removed +* `ConnectionInfo::get`. [#2487] + [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 +[#2487]: https://github.com/actix/actix-web/pull/2487 [#2491]: https://github.com/actix/actix-web/pull/2491 [#2492]: https://github.com/actix/actix-web/pull/2492 [#2493]: https://github.com/actix/actix-web/pull/2493 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f435784d8..3e62ac2d1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -16,6 +16,8 @@ * `impl Display` for `header::Quality`. [#2486] * Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] * `Request::take_conn_data()`. [#2491] +* `Request::take_req_data()`. [#2487] +* `impl Clone` for `RequestHead`. [#2487] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -40,6 +42,7 @@ [#2468]: https://github.com/actix/actix-web/pull/2468 [#1920]: https://github.com/actix/actix-web/pull/1920 [#2486]: https://github.com/actix/actix-web/pull/2486 +[#2487]: https://github.com/actix/actix-web/pull/2487 [#2488]: https://github.com/actix/actix-web/pull/2488 [#2491]: https://github.com/actix/actix-web/pull/2491 diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 0a46a89f9..a29903cc4 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,8 +1,9 @@ use std::{convert::Infallible, io}; -use actix_http::{HttpService, Response, StatusCode}; +use actix_http::{ + header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, +}; use actix_server::Server; -use http::header::HeaderValue; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -13,12 +14,21 @@ async fn main() -> io::Result<()> { HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .finish(|req| async move { + .on_connect_ext(|_, ext| { + ext.insert(42u32); + }) + .finish(|req: Request| async move { log::info!("{:?}", req); let mut res = Response::build(StatusCode::OK); res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); + let forty_two = req.extensions().get::().unwrap().to_string(); + res.insert_header(( + "x-forty-two", + HeaderValue::from_str(&forty_two).unwrap(), + )); + Ok::<_, Infallible>(res.body("Hello world!")) }) .tcp() diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 164919d87..60b769d13 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -19,7 +19,7 @@ impl Extensions { #[inline] pub fn new() -> Extensions { Extensions { - map: AHashMap::default(), + map: AHashMap::new(), } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index c8e1ce6db..31c2db718 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -44,13 +44,12 @@ pub trait Head: Default + 'static { F: FnOnce(&MessagePool) -> R; } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RequestHead { pub method: Method, pub uri: Uri, pub version: Version, pub headers: HeaderMap, - pub extensions: RefCell, pub peer_addr: Option, flags: Flags, } @@ -62,7 +61,6 @@ impl Default for RequestHead { uri: Uri::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - extensions: RefCell::new(Extensions::new()), peer_addr: None, flags: Flags::empty(), } @@ -73,7 +71,6 @@ impl Head for RequestHead { fn clear(&mut self) { self.flags = Flags::empty(); self.headers.clear(); - self.extensions.get_mut().clear(); } fn with_pool(f: F) -> R @@ -85,18 +82,6 @@ impl Head for RequestHead { } impl RequestHead { - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.extensions.borrow_mut() - } - /// Read the message headers. pub fn headers(&self) -> &HeaderMap { &self.headers diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 78c0527b5..c7752d470 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,8 +1,8 @@ //! HTTP requests. use std::{ - cell::{Ref, RefMut}, - fmt, net, + cell::{Ref, RefCell, RefMut}, + fmt, mem, net, rc::Rc, str, }; @@ -22,6 +22,7 @@ pub struct Request

{ pub(crate) payload: Payload

, pub(crate) head: Message, pub(crate) conn_data: Option>, + pub(crate) req_data: RefCell, } impl

HttpMessage for Request

{ @@ -33,19 +34,19 @@ impl

HttpMessage for Request

{ } fn take_payload(&mut self) -> Payload

{ - std::mem::replace(&mut self.payload, Payload::None) + mem::replace(&mut self.payload, Payload::None) } /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions() + self.req_data.borrow() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head.extensions_mut() + self.req_data.borrow_mut() } } @@ -54,6 +55,7 @@ impl From> for Request { Request { head, payload: Payload::None, + req_data: RefCell::new(Extensions::default()), conn_data: None, } } @@ -65,6 +67,7 @@ impl Request { Request { head: Message::new(), payload: Payload::None, + req_data: RefCell::new(Extensions::default()), conn_data: None, } } @@ -76,6 +79,7 @@ impl

Request

{ Request { payload, head: Message::new(), + req_data: RefCell::new(Extensions::default()), conn_data: None, } } @@ -88,6 +92,7 @@ impl

Request

{ Request { payload, head: self.head, + req_data: self.req_data, conn_data: self.conn_data, }, pl, @@ -101,7 +106,7 @@ impl

Request

{ /// Get request's payload pub fn take_payload(&mut self) -> Payload

{ - std::mem::replace(&mut self.payload, Payload::None) + mem::replace(&mut self.payload, Payload::None) } /// Split request into request head and payload @@ -124,7 +129,7 @@ impl

Request

{ /// Mutable reference to the message's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers + &mut self.head.headers } /// Request's uri. @@ -136,7 +141,7 @@ impl

Request

{ /// Mutable reference to the request's uri. #[inline] pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.head_mut().uri + &mut self.head.uri } /// Read the Request method. @@ -198,6 +203,11 @@ impl

Request

{ pub fn take_conn_data(&mut self) -> Option> { self.conn_data.take() } + + /// Returns the request data container, leaving an empty one in it's place. + pub fn take_req_data(&mut self) -> Extensions { + mem::take(&mut self.req_data.get_mut()) + } } impl

fmt::Debug for Request

{ diff --git a/src/app_service.rs b/src/app_service.rs index 5dfc3b5ae..cc5100f04 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -198,6 +198,7 @@ where actix_service::forward_ready!(service); fn call(&self, mut req: Request) -> Self::Future { + let req_data = Rc::new(RefCell::new(req.take_req_data())); let conn_data = req.take_conn_data(); let (head, payload) = req.into_parts(); @@ -207,6 +208,7 @@ where inner.path.reset(); inner.head = head; inner.conn_data = conn_data; + inner.req_data = req_data; req } else { HttpRequest::new( @@ -215,6 +217,7 @@ where self.app_state.clone(), self.app_data.clone(), conn_data, + req_data, ) }; self.service.call(ServiceRequest::new(req, payload)) diff --git a/src/info.rs b/src/info.rs index d928a1e63..71194b24d 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,4 +1,4 @@ -use std::{cell::Ref, convert::Infallible, net::SocketAddr}; +use std::{convert::Infallible, net::SocketAddr}; use actix_utils::future::{err, ok, Ready}; use derive_more::{Display, Error}; @@ -72,15 +72,7 @@ pub struct ConnectionInfo { } impl ConnectionInfo { - /// Create *ConnectionInfo* instance for a request. - pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> { - if !req.extensions().contains::() { - req.extensions_mut().insert(ConnectionInfo::new(req, cfg)); - } - Ref::map(req.extensions(), |e| e.get().unwrap()) - } - - fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { + pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; let mut realip_remote_addr = None; diff --git a/src/request.rs b/src/request.rs index d99849eef..d84722d95 100644 --- a/src/request.rs +++ b/src/request.rs @@ -38,6 +38,7 @@ pub(crate) struct HttpRequestInner { pub(crate) path: Path, pub(crate) app_data: SmallVec<[Rc; 4]>, pub(crate) conn_data: Option>, + pub(crate) req_data: Rc>, app_state: Rc, } @@ -49,6 +50,7 @@ impl HttpRequest { app_state: Rc, app_data: Rc, conn_data: Option>, + req_data: Rc>, ) -> HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); @@ -60,6 +62,7 @@ impl HttpRequest { app_state, app_data: data, conn_data, + req_data, }), } } @@ -156,16 +159,12 @@ impl HttpRequest { self.resource_map().match_name(self.path()) } - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.head().extensions() + pub fn req_data(&self) -> Ref<'_, Extensions> { + self.inner.req_data.borrow() } - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head().extensions_mut() + pub fn req_data_mut(&self) -> RefMut<'_, Extensions> { + self.inner.req_data.borrow_mut() } /// Returns a reference a piece of connection data set in an [on-connect] callback. @@ -248,7 +247,12 @@ impl HttpRequest { /// borrowed. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { - ConnectionInfo::get(self.head(), self.app_config()) + if !self.extensions().contains::() { + let info = ConnectionInfo::new(self.head(), &*self.app_config()); + self.extensions_mut().insert(info); + } + + Ref::map(self.extensions(), |e| e.get().unwrap()) } /// App config @@ -321,21 +325,18 @@ impl HttpMessage for HttpRequest { type Stream = (); #[inline] - /// Returns Request's headers. fn headers(&self) -> &HeaderMap { &self.head().headers } - /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.inner.head.extensions() + self.req_data() } - /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.inner.head.extensions_mut() + self.req_data_mut() } #[inline] @@ -348,14 +349,15 @@ impl Drop for HttpRequest { fn drop(&mut self) { // if possible, contribute to current worker's HttpRequest allocation pool - // This relies on no Weak exists anywhere.(There is none) + // This relies on no Weak exists anywhere. (There is none.) if let Some(inner) = Rc::get_mut(&mut self.inner) { if inner.app_state.pool().is_available() { // clear additional app_data and keep the root one for reuse. inner.app_data.truncate(1); - // inner is borrowed mut here. get head's Extension mutably - // to reduce borrow check - inner.head.extensions.get_mut().clear(); + + // Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also + // we know the req_data Rc will not have any cloned at this point to unwrap is okay. + Rc::get_mut(&mut inner.req_data).unwrap().get_mut().clear(); // a re-borrow of pool is necessary here. let req = self.inner.clone(); diff --git a/src/request_data.rs b/src/request_data.rs index 575dc1eb3..680f3e566 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -33,12 +33,11 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H /// req: HttpRequest, /// opt_flag: Option>, /// ) -> impl Responder { -/// // use an optional extractor if the middleware is -/// // not guaranteed to add this type of requests data +/// // use an option extractor if middleware is not guaranteed to add this type of req data /// if let Some(flag) = opt_flag { -/// assert_eq!(&flag.into_inner(), req.extensions().get::().unwrap()); +/// assert_eq!(&flag.into_inner(), req.req_data().get::().unwrap()); /// } -/// +/// /// HttpResponse::Ok() /// } /// ``` @@ -68,7 +67,7 @@ impl FromRequest for ReqData { type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if let Some(st) = req.extensions().get::() { + if let Some(st) = req.req_data().get::() { ok(ReqData(st.clone())) } else { log::debug!( diff --git a/src/service.rs b/src/service.rs index d56752f13..88f2ba97a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -194,7 +194,7 @@ impl ServiceRequest { /// Get *ConnectionInfo* for the current request. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { - ConnectionInfo::get(self.head(), &*self.app_config()) + self.req.connection_info() } /// Get a reference to the Path parameters. diff --git a/src/test.rs b/src/test.rs index bff9c62dc..cfb3ef8f2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -581,7 +581,14 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); ServiceRequest::new( - HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None), + HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ), payload, ) } @@ -599,7 +606,14 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None) + HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ) } /// Complete request creation and generate `HttpRequest` and `Payload` instances @@ -610,7 +624,14 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None); + let req = HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ); (req, payload) } From 816d68dee800aafae123802d126c0227a723535f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 9 Dec 2021 00:46:28 +0000 Subject: [PATCH 156/861] pin h2 temporarily --- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6216af3d1..5c5a0cc86 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -56,7 +56,7 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -h2 = "0.3.1" +h2 = "=0.3.7" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 836241d46..cdbd0b6aa 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -72,7 +72,7 @@ cfg-if = "1" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -h2 = "0.3" +h2 = "=0.3.7" http = "0.2.5" itoa = "0.4" log =" 0.4" From 69fa17f66f8404fda585bf22fc6609a917bf5baa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 9 Dec 2021 11:27:29 +0000 Subject: [PATCH 157/861] clean future h2 dispatcher --- actix-http/src/h2/dispatcher.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index da2d612f1..8fbefe6de 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -19,13 +19,13 @@ use h2::{ server::{Connection, SendResponse}, Ping, PingPong, }; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use log::{error, trace}; use pin_project_lite::pin_project; use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, + header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, service::HttpFlow, Extensions, OnConnectData, Payload, Request, Response, ResponseHead, }; @@ -217,25 +217,28 @@ where return Ok(()); } - // poll response body and send chunks to client. + // poll response body and send chunks to client actix_rt::pin!(body); while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await { let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?; 'send: loop { + let chunk_size = cmp::min(chunk.len(), CHUNK_SIZE); + // reserve enough space and wait for stream ready. - stream.reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE)); + stream.reserve_capacity(chunk_size); match poll_fn(|cx| stream.poll_capacity(cx)).await { // No capacity left. drop body and return. None => return Ok(()), - Some(res) => { - // Split chuck to writeable size and send to client. - let cap = res.map_err(DispatchError::SendData)?; + Some(Err(err)) => return Err(DispatchError::SendData(err)), + + Some(Ok(cap)) => { + // split chunk to writeable size and send to client let len = chunk.len(); - let bytes = chunk.split_to(cmp::min(cap, len)); + let bytes = chunk.split_to(cmp::min(len, cap)); stream .send_data(bytes, false) From 774ac7fec435b465f229f44f16fb74c7de971a40 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 9 Dec 2021 13:52:35 +0000 Subject: [PATCH 158/861] provide optimisation path for single-chunk body types (#2497) --- actix-http/CHANGES.md | 2 + actix-http/src/body/boxed.rs | 28 ++++ actix-http/src/body/either.rs | 14 ++ actix-http/src/body/message_body.rs | 230 ++++++++++++++++++++++++++-- actix-http/src/body/none.rs | 10 ++ actix-http/src/encoding/encoder.rs | 72 +++++++-- actix-http/tests/test_openssl.rs | 2 +- 7 files changed, 328 insertions(+), 30 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3e62ac2d1..7081361e4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -18,6 +18,7 @@ * `Request::take_conn_data()`. [#2491] * `Request::take_req_data()`. [#2487] * `impl Clone` for `RequestHead`. [#2487] +* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -45,6 +46,7 @@ [#2487]: https://github.com/actix/actix-web/pull/2487 [#2488]: https://github.com/actix/actix-web/pull/2488 [#2491]: https://github.com/actix/actix-web/pull/2491 +[#2497]: https://github.com/actix/actix-web/pull/2497 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index c39da10c0..d2469e986 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -51,6 +51,34 @@ impl MessageBody for BoxBody { .poll_next(cx) .map_err(|err| Error::new_body().with_cause(err)) } + + fn is_complete_body(&self) -> bool { + self.0.is_complete_body() + } + + fn take_complete_body(&mut self) -> Bytes { + debug_assert!( + self.is_complete_body(), + "boxed type does not allow taking complete body; caller should make sure to \ + call `is_complete_body` first", + ); + + // we do not have DerefMut access to call take_complete_body directly but since + // is_complete_body is true we should expect the entire bytes chunk in one poll_next + + let waker = futures_util::task::noop_waker(); + let mut cx = Context::from_waker(&waker); + + match self.as_pin_mut().poll_next(&mut cx) { + Poll::Ready(Some(Ok(data))) => data, + _ => { + panic!( + "boxed type indicated it allows taking complete body but failed to \ + return Bytes when polled", + ); + } + } + } } #[cfg(test)] diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index 6169ee627..6135d834d 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -67,6 +67,20 @@ where .map_err(|err| Error::new_body().with_cause(err)), } } + + fn is_complete_body(&self) -> bool { + match self { + EitherBody::Left { body } => body.is_complete_body(), + EitherBody::Right { body } => body.is_complete_body(), + } + } + + fn take_complete_body(&mut self) -> Bytes { + match self { + EitherBody::Left { body } => body.take_complete_body(), + EitherBody::Right { body } => body.take_complete_body(), + } + } } #[cfg(test)] diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 053b6f286..e4020d2af 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -25,10 +25,58 @@ pub trait MessageBody { fn size(&self) -> BodySize; /// Attempt to pull out the next chunk of body bytes. + // TODO: expand documentation fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>>; + + /// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`. + /// + /// This method's implementation should agree with [`take_complete_body`] and should always be + /// checked before taking the body. + /// + /// The default implementation returns `false. + /// + /// [`take_complete_body`]: MessageBody::take_complete_body + fn is_complete_body(&self) -> bool { + false + } + + /// Returns the complete chunk of body bytes. + /// + /// Implementors of this method should note the following: + /// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of + /// performing this check is delegated to the caller. + /// - If the result of [`is_complete_body`] is conditional, that condition should be given + /// equivalent attention here. + /// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic. + /// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless + /// the chunk is guaranteed to be empty. + /// + /// The default implementation panics unconditionally, indicating a control flow bug in the + /// calling code. + /// + /// # Panics + /// With a correct implementation, panics if called without first checking [`is_complete_body`]. + /// + /// [`is_complete_body`]: MessageBody::is_complete_body + /// [`take_complete_body`]: MessageBody::take_complete_body + /// [`poll_next`]: MessageBody::poll_next + fn take_complete_body(&mut self) -> Bytes { + assert!( + self.is_complete_body(), + "type ({}) allows taking complete body but did not provide an implementation \ + of `take_complete_body`", + std::any::type_name::() + ); + + unimplemented!( + "type ({}) does not allow taking complete body; caller should make sure to \ + check `is_complete_body` first", + std::any::type_name::() + ); + } } mod foreign_impls { @@ -49,6 +97,14 @@ mod foreign_impls { ) -> Poll>> { match *self {} } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + match *self {} + } } impl MessageBody for () { @@ -66,6 +122,16 @@ mod foreign_impls { ) -> Poll>> { Poll::Ready(None) } + + #[inline] + fn is_complete_body(&self) -> bool { + true + } + + #[inline] + fn take_complete_body(&mut self) -> Bytes { + Bytes::new() + } } impl MessageBody for Box @@ -86,6 +152,16 @@ mod foreign_impls { ) -> Poll>> { Pin::new(self.get_mut().as_mut()).poll_next(cx) } + + #[inline] + fn is_complete_body(&self) -> bool { + self.as_ref().is_complete_body() + } + + #[inline] + fn take_complete_body(&mut self) -> Bytes { + self.as_mut().take_complete_body() + } } impl MessageBody for Pin> @@ -106,6 +182,38 @@ mod foreign_impls { ) -> Poll>> { self.as_mut().poll_next(cx) } + + #[inline] + fn is_complete_body(&self) -> bool { + self.as_ref().is_complete_body() + } + + #[inline] + fn take_complete_body(&mut self) -> Bytes { + debug_assert!( + self.is_complete_body(), + "inner type \"{}\" does not allow taking complete body; caller should make sure to \ + call `is_complete_body` first", + std::any::type_name::(), + ); + + // we do not have DerefMut access to call take_complete_body directly but since + // is_complete_body is true we should expect the entire bytes chunk in one poll_next + + let waker = futures_util::task::noop_waker(); + let mut cx = Context::from_waker(&waker); + + match self.as_mut().poll_next(&mut cx) { + Poll::Ready(Some(Ok(data))) => data, + _ => { + panic!( + "inner type \"{}\" indicated it allows taking complete body but failed to \ + return Bytes when polled", + std::any::type_name::() + ); + } + } + } } impl MessageBody for &'static [u8] { @@ -116,17 +224,23 @@ mod foreign_impls { } fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - let bytes = mem::take(self.get_mut()); - let bytes = Bytes::from_static(bytes); - Poll::Ready(Some(Ok(bytes))) + Poll::Ready(Some(Ok(self.take_complete_body()))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + Bytes::from_static(mem::take(self)) + } } impl MessageBody for Bytes { @@ -137,16 +251,23 @@ mod foreign_impls { } fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - let bytes = mem::take(self.get_mut()); - Poll::Ready(Some(Ok(bytes))) + Poll::Ready(Some(Ok(self.take_complete_body()))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + mem::take(self) + } } impl MessageBody for BytesMut { @@ -157,16 +278,23 @@ mod foreign_impls { } fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - let bytes = mem::take(self.get_mut()).freeze(); - Poll::Ready(Some(Ok(bytes))) + Poll::Ready(Some(Ok(self.take_complete_body()))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + mem::take(self).freeze() + } } impl MessageBody for Vec { @@ -177,16 +305,23 @@ mod foreign_impls { } fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - let bytes = mem::take(self.get_mut()); - Poll::Ready(Some(Ok(Bytes::from(bytes)))) + Poll::Ready(Some(Ok(self.take_complete_body()))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + Bytes::from(mem::take(self)) + } } impl MessageBody for &'static str { @@ -208,6 +343,14 @@ mod foreign_impls { Poll::Ready(Some(Ok(bytes))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + Bytes::from_static(mem::take(self).as_bytes()) + } } impl MessageBody for String { @@ -228,6 +371,14 @@ mod foreign_impls { Poll::Ready(Some(Ok(Bytes::from(string)))) } } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + Bytes::from(mem::take(self)) + } } impl MessageBody for bytestring::ByteString { @@ -244,6 +395,14 @@ mod foreign_impls { let string = mem::take(self.get_mut()); Poll::Ready(Some(Ok(string.into_bytes()))) } + + fn is_complete_body(&self) -> bool { + true + } + + fn take_complete_body(&mut self) -> Bytes { + mem::take(self).into_bytes() + } } } @@ -406,6 +565,51 @@ mod tests { assert_poll_next!(pl, Bytes::from("test")); } + #[test] + fn take_string() { + let mut data = "test".repeat(2); + let data_bytes = Bytes::from(data.clone()); + assert!(data.is_complete_body()); + assert_eq!(data.take_complete_body(), data_bytes); + + let mut big_data = "test".repeat(64 * 1024); + let data_bytes = Bytes::from(big_data.clone()); + assert!(big_data.is_complete_body()); + assert_eq!(big_data.take_complete_body(), data_bytes); + } + + #[test] + fn take_boxed_equivalence() { + let mut data = Bytes::from_static(b"test"); + assert!(data.is_complete_body()); + assert_eq!(data.take_complete_body(), b"test".as_ref()); + + let mut data = Box::new(Bytes::from_static(b"test")); + assert!(data.is_complete_body()); + assert_eq!(data.take_complete_body(), b"test".as_ref()); + + let mut data = Box::pin(Bytes::from_static(b"test")); + assert!(data.is_complete_body()); + assert_eq!(data.take_complete_body(), b"test".as_ref()); + } + + #[test] + fn take_policy() { + let mut data = Bytes::from_static(b"test"); + // first call returns chunk + assert_eq!(data.take_complete_body(), b"test".as_ref()); + // second call returns empty + assert_eq!(data.take_complete_body(), b"".as_ref()); + + let waker = futures_util::task::noop_waker(); + let mut cx = Context::from_waker(&waker); + let mut data = Bytes::from_static(b"test"); + // take returns whole chunk + assert_eq!(data.take_complete_body(), b"test".as_ref()); + // subsequent poll_next returns None + assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None)); + } + // down-casting used to be done with a method on MessageBody trait // test is kept to demonstrate equivalence of Any trait #[actix_rt::test] diff --git a/actix-http/src/body/none.rs b/actix-http/src/body/none.rs index 0fc7c8c9f..bb494078f 100644 --- a/actix-http/src/body/none.rs +++ b/actix-http/src/body/none.rs @@ -40,4 +40,14 @@ impl MessageBody for None { ) -> Poll>> { Poll::Ready(Option::None) } + + #[inline] + fn is_complete_body(&self) -> bool { + true + } + + #[inline] + fn take_complete_body(&mut self) -> Bytes { + Bytes::new() + } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0886221cc..fa294ab0d 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -53,32 +53,32 @@ impl Encoder { } } - pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { + pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT || encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto); - match body.size() { - // no need to compress an empty body - BodySize::None => return Self::none(), - - // we cannot assume that Sized is not a stream - BodySize::Sized(_) | BodySize::Stream => {} + // no need to compress an empty body + if matches!(body.size(), BodySize::None) { + return Self::none(); } - // TODO potentially some optimisation for single-chunk responses here by trying to read the - // payload eagerly, stopping after 2 polls if the first is a chunk and the second is None + let body = if body.is_complete_body() { + let body = body.take_complete_body(); + EncoderBody::Full { body } + } else { + EncoderBody::Stream { body } + }; if can_encode { // Modify response body only if encoder is set if let Some(enc) = ContentEncoder::encoder(encoding) { update_head(encoding, head); - head.no_chunking(false); return Encoder { - body: EncoderBody::Stream { body }, + body, encoder: Some(enc), fut: None, eof: false, @@ -87,7 +87,7 @@ impl Encoder { } Encoder { - body: EncoderBody::Stream { body }, + body, encoder: None, fut: None, eof: false, @@ -99,6 +99,7 @@ pin_project! { #[project = EncoderBodyProj] enum EncoderBody { None, + Full { body: Bytes }, Stream { #[pin] body: B }, } } @@ -112,6 +113,7 @@ where fn size(&self) -> BodySize { match self { EncoderBody::None => BodySize::None, + EncoderBody::Full { body } => body.size(), EncoderBody::Stream { body } => body.size(), } } @@ -122,12 +124,32 @@ where ) -> Poll>> { match self.project() { EncoderBodyProj::None => Poll::Ready(None), - + EncoderBodyProj::Full { body } => { + Pin::new(body).poll_next(cx).map_err(|err| match err {}) + } EncoderBodyProj::Stream { body } => body .poll_next(cx) .map_err(|err| EncoderError::Body(err.into())), } } + + fn is_complete_body(&self) -> bool { + match self { + EncoderBody::None => true, + EncoderBody::Full { .. } => true, + EncoderBody::Stream { .. } => false, + } + } + + fn take_complete_body(&mut self) -> Bytes { + match self { + EncoderBody::None => Bytes::new(), + EncoderBody::Full { body } => body.take_complete_body(), + EncoderBody::Stream { .. } => { + panic!("EncoderBody::Stream variant cannot be taken") + } + } + } } impl MessageBody for Encoder @@ -137,10 +159,10 @@ where type Error = EncoderError; fn size(&self) -> BodySize { - if self.encoder.is_none() { - self.body.size() - } else { + if self.encoder.is_some() { BodySize::Stream + } else { + self.body.size() } } @@ -211,6 +233,22 @@ where } } } + + fn is_complete_body(&self) -> bool { + if self.encoder.is_some() { + false + } else { + self.body.is_complete_body() + } + } + + fn take_complete_body(&mut self) -> Bytes { + if self.encoder.is_some() { + panic!("compressed body stream cannot be taken") + } else { + self.body.take_complete_body() + } + } } fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { @@ -218,6 +256,8 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { header::CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str()), ); + + head.no_chunking(false); } enum ContentEncoder { diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 0c373b8b2..1e371473f 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -101,7 +101,7 @@ async fn test_h2_1() -> io::Result<()> { #[actix_rt::test] async fn test_h2_body() -> io::Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB let mut srv = test_server(move || { HttpService::build() .h2(|mut req: Request<_>| async move { From f9348d71297c62e623aceb77112a9ec256ceaf3d Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Thu, 9 Dec 2021 17:57:27 +0300 Subject: [PATCH 159/861] add ServiceResponse::into_parts (#2499) --- CHANGES.md | 2 ++ src/service.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 365c89af9..b31624bee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] * `HttpRequest::{req_data,req_data_mut}`. [#2487] +* `ServiceResponse::into_parts`. [#2499] ### Changed * Rename `Accept::{mime_precedence => ranked}`. [#2480] @@ -37,6 +38,7 @@ [#2491]: https://github.com/actix/actix-web/pull/2491 [#2492]: https://github.com/actix/actix-web/pull/2492 [#2493]: https://github.com/actix/actix-web/pull/2493 +[#2499]: https://github.com/actix/actix-web/pull/2499 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/service.rs b/src/service.rs index 88f2ba97a..36b3858e6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -410,6 +410,12 @@ impl ServiceResponse { self.response.headers_mut() } + /// Destructures `ServiceResponse` into request and response components. + #[inline] + pub fn into_parts(self) -> (HttpRequest, HttpResponse) { + (self.request, self.response) + } + /// Extract response body #[inline] pub fn into_body(self) -> B { From f62383a9751ad9446cbe941cb53167c4a79b75d3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 10 Dec 2021 22:13:12 +0000 Subject: [PATCH 160/861] unpin h2 --- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5c5a0cc86..ef5f84e4d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -56,7 +56,7 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -h2 = "=0.3.7" +h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cdbd0b6aa..ed1196f62 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -72,7 +72,7 @@ cfg-if = "1" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -h2 = "=0.3.7" +h2 = "0.3.9" http = "0.2.5" itoa = "0.4" log =" 0.4" From 65dd5dfa7b87d1597f0937c509d53e4322e37aa5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:21:30 +0000 Subject: [PATCH 161/861] bump script updates referenced crate versions --- scripts/bump | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/scripts/bump b/scripts/bump index 8b6a3c424..73372ff05 100755 --- a/scripts/bump +++ b/scripts/bump @@ -82,8 +82,33 @@ rm -f $README_FILE.bak echo "manifest, changelog, and readme updated" echo echo "check other references:" -rg "$PACKAGE_NAME =" || true -rg "package = \"$PACKAGE_NAME\"" || true +rg --glob='**/Cargo.toml' "\ +${PACKAGE_NAME} ?= ?\"[^\"]+\"\ +|${PACKAGE_NAME} ?=.*version ?= ?\"([^\"]+)\"\ +|package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\"([^\"]+)\"\ +|version ?= ?\"([^\"]+)\".*package ?= ?\"${PACKAGE_NAME}\"" || true + +echo +read -p "Update all references: (y/N) " UPDATE_REFERENCES +UPDATE_REFERENCES="${UPDATE_REFERENCES:-n}" + +if [ "$UPDATE_REFERENCES" = 'y' ] || [ "$UPDATE_REFERENCES" = 'Y' ]; then + + for f in $(fd Cargo.toml); do + sed -i.bak -E \ + "s/^(${PACKAGE_NAME} ?= ?\")[^\"]+(\")$/\1${NEW_VERSION}\2/g" $f + sed -i.bak -E \ + "s/^(${PACKAGE_NAME} ?=.*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f + sed -i.bak -E \ + "s/^(.*package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f + sed -i.bak -E \ + "s/^(.*version ?= ?\")[^\"]+(\".*package ?= ?\"${PACKAGE_NAME}\".*)$/\1${NEW_VERSION}\2/g" $f + + # remove backup file + rm -f $f.bak + done + +fi if [ $MACOS ]; then printf "prepare $PACKAGE_NAME release $NEW_VERSION" | pbcopy From d0f4c809cad35c7a5b3e4f23c91d41391e418df1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:22:09 +0000 Subject: [PATCH 162/861] prepare actix-http release 3.0.0-beta.15 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cee0680a5..cd7fcdfa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" actix-router = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.5" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6b6d6d245..932d20ec8 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -23,7 +23,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false } -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" actix-service = "2" actix-utils = "3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 7a22cbcc1..2e847c1cc 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7081361e4..fe47902f7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.15 - 2021-12-11 ### Added * Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] * HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ef5f84e4d..5d9035f78 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.14" +version = "3.0.0-beta.15" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] diff --git a/actix-http/README.md b/actix-http/README.md index 92b86d2a3..a2aa41333 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.14)](https://docs.rs/actix-http/3.0.0-beta.14) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.15)](https://docs.rs/actix-http/3.0.0-beta.15) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.14) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.15) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 04a1d75ee..8178e2ffe 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index dcaa3e9a3..52e91a6fd 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" actix-http-test = "3.0.0-beta.7" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 28b5b29ea..63f517adc 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" actix-web = { version = "4.0.0-beta.11", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ed1196f62..1dd4df455 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.14" +actix-http = "3.0.0-beta.15" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.14", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.15", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } actix-utils = "3.0.0" actix-server = "2.0.0-rc.1" From e1cdabe5cb803af2041e51ff1fdba2e602db5dbc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:28:38 +0000 Subject: [PATCH 163/861] prepare awc release 3.0.0-beta.13 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- scripts/bump | 2 ++ 8 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cd7fcdfa2..79ee750a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.11", features = ["openssl"] } +awc = { version = "3.0.0-beta.13", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2e847c1cc..ee0d14ee6 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-rc.1" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.1" -awc = { version = "3.0.0-beta.11", default-features = false } +awc = { version = "3.0.0-beta.13", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 52e91a6fd..79eb0689d 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } actix-rt = "2.1" -awc = { version = "3.0.0-beta.11", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 63f517adc..fa3bb8119 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.7" +awc = { version = "3.0.0-beta.13", default-features = false } -awc = { version = "3.0.0-beta.11", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ab3362b72..798b2ce6b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.13 - 2021-12-11 +* No significant changes since `3.0.0-beta.12`. + + ## 3.0.0-beta.12 - 2021-11-30 * Update `actix-tls` to `3.0.0-rc.1`. [#2474] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1dd4df455..9a64c6579 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.12" +version = "3.0.0-beta.13" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index b0faedc68..f3c5452fc 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.12)](https://docs.rs/awc/3.0.0-beta.12) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.13)](https://docs.rs/awc/3.0.0-beta.13) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.12/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.12) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.13/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.13) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/scripts/bump b/scripts/bump index 73372ff05..0c360569d 100755 --- a/scripts/bump +++ b/scripts/bump @@ -41,6 +41,8 @@ cat "$CHANGELOG_FILE" | # if word count of changelog chunk is 0 then insert filler changelog chunk if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then echo "* No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE" + echo >>"$CHANGE_CHUNK_FILE" + echo >>"$CHANGE_CHUNK_FILE" fi if [ -n "${2-}" ]; then From cc37be9700f7c2d373cfae01dc1289c652743105 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:30:12 +0000 Subject: [PATCH 164/861] prepare actix-web release 4.0.0-beta.14 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 6 +++--- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 5 +++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 4 ++-- awc/Cargo.toml | 6 +++--- 11 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b31624bee..3e0b12d9e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.14 - 2021-12-11 ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * `AcceptEncoding` typed header. [#2482] diff --git a/Cargo.toml b/Cargo.toml index 79ee750a3..9394de71d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.13" +version = "4.0.0-beta.14" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index c363ece9b..4a1671905 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.13)](https://docs.rs/actix-web/4.0.0-beta.13) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.14)](https://docs.rs/actix-web/4.0.0-beta.14) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.13) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.14)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 932d20ec8..6480c6fd0 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,17 +22,17 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-web = { version = "4.0.0-beta.11", default-features = false } actix-http = "3.0.0-beta.15" actix-service = "2" actix-utils = "3" +actix-web = { version = "4.0.0-beta.14", default-features = false } askama_escape = "0.10" bitflags = "1" bytes = "1" +derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } http-range = "0.1.4" -derive_more = "0.99.5" log = "0.4" mime = "0.3" mime_guess = "2.0.1" @@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" -actix-web = "4.0.0-beta.11" actix-test = "0.1.0-beta.7" +actix-web = "4.0.0-beta.14" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index ee0d14ee6..86acae415 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.15" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5d9035f78..fff9fcc16 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,10 +81,11 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } +actix-server = "2.0.0-rc.1" actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } -actix-web = "4.0.0-beta.13" +actix-web = "4.0.0-beta.14" + async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 8178e2ffe..9704c255f 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -14,8 +14,8 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.11", default-features = false } actix-utils = "3.0.0" +actix-web = { version = "4.0.0-beta.14", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 79eb0689d..70497d033 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -31,10 +31,10 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] actix-codec = "0.4.1" actix-http = "3.0.0-beta.15" actix-http-test = "3.0.0-beta.7" +actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] } -actix-rt = "2.1" +actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index fa3bb8119..bd8ca9661 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.15" -actix-web = { version = "4.0.0-beta.11", default-features = false } +actix-web = { version = "4.0.0-beta.14", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 8497f0b23..e856aaaf9 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -21,11 +21,11 @@ proc-macro2 = "1" actix-router = "0.5.0-beta.2" [dev-dependencies] -actix-rt = "2.2" actix-macros = "0.2.3" +actix-rt = "2.2" actix-test = "0.1.0-beta.7" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.11" +actix-web = "4.0.0-beta.14" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9a64c6579..eb5ef1e4a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-web = { version = "4.0.0-beta.11", features = ["openssl"] } actix-http = { version = "3.0.0-beta.15", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } -actix-utils = "3.0.0" actix-server = "2.0.0-rc.1" -actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } +actix-utils = "3.0.0" +actix-web = { version = "4.0.0-beta.14", features = ["openssl"] } brotli2 = "0.3.2" env_logger = "0.9" From ed2f5b40b9ed91b3e1c8e571978e34ed9105e8b1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:31:41 +0000 Subject: [PATCH 165/861] prepare actix-files release 0.6.0-beta.10 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 63d8efc3f..d6b39e28f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.10 - 2021-12-11 +* No significant changes since `0.6.0-beta.9`. + + ## 0.6.0-beta.9 - 2021-11-22 * Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] * Add `NamedFile::open_async`. [#2408] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6480c6fd0..033e01681 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.9" +version = "0.6.0-beta.10" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index 84e556fa9..d686e255c 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.9)](https://docs.rs/actix-files/0.6.0-beta.9) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.9/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.9) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.10/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.10) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 0cd7c1768270dd3c4245ab9dae331e00be1da858 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:32:00 +0000 Subject: [PATCH 166/861] prepare actix-http-test release 3.0.0-beta.9 --- actix-http-test/CHANGES.md | 4 ++++ actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 6984e5962..156012168 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.9 - 2021-12-11 +* No significant changes since `3.0.0-beta.8`. + + ## 3.0.0-beta.8 - 2021-11-30 * Update `actix-tls` to `3.0.0-rc.1`. [#2474] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 86acae415..449fa342e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.8" +version = "3.0.0-beta.9" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index c3e99d259..a0a8f1cd8 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.8)](https://docs.rs/actix-http-test/3.0.0-beta.8) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http-test/3.0.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.8) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.9) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index fff9fcc16..374a55a62 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } actix-web = "4.0.0-beta.14" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 70497d033..367aa7514 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" actix-http = "3.0.0-beta.15" -actix-http-test = "3.0.0-beta.7" +actix-http-test = "3.0.0-beta.9" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index eb5ef1e4a..e1411fcf6 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-http = { version = "3.0.0-beta.15", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } From 6481a5fb738cdb9b181fdfe0e0cbfa98f9397c56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:32:26 +0000 Subject: [PATCH 167/861] prepare actix-test release 0.1.0-beta.8 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 4 ++++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9394de71d..05e4c40bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.13", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 033e01681..edb7cfab4 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.7" +actix-test = "0.1.0-beta.8" actix-web = "4.0.0-beta.14" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index b739011f0..ec7d3e8d1 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.8 - 2021-12-11 +* No significant changes since `0.1.0-beta.7`. + + ## 0.1.0-beta.7 - 2021-11-22 * Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 367aa7514..71f99f791 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.7" +version = "0.1.0-beta.8" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index bd8ca9661..26388fcc3 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.7" +actix-test = "0.1.0-beta.8" awc = { version = "3.0.0-beta.13", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index e856aaaf9..6ba083c0a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.7" +actix-test = "0.1.0-beta.8" actix-utils = "3.0.0" actix-web = "4.0.0-beta.14" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e1411fcf6..48ae27df0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-http = { version = "3.0.0-beta.15", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" -actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.14", features = ["openssl"] } From fc4e9ff96bf2b41984eb7c322f25e9819a4f5f2b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:33:31 +0000 Subject: [PATCH 168/861] prepare actix-web-codegen release 0.5.0-beta.6 --- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05e4c40bc..96e2dd797 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true actix-http = "3.0.0-beta.15" actix-router = "0.5.0-beta.2" -actix-web-codegen = "0.5.0-beta.5" +actix-web-codegen = "0.5.0-beta.6" ahash = "0.7" bytes = "1" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 3811ef030..309274563 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.5.0-beta.6 - 2021-12-11 +* No significant changes since `0.5.0-beta.5`. + + ## 0.5.0-beta.5 - 2021-10-20 * Improve error recovery potential when macro input is invalid. [#2410] * Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 6ba083c0a..211f19da6 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.5" +version = "0.5.0-beta.6" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 2ffd5b31c..f05d3f22c 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.5)](https://docs.rs/actix-web-codegen/0.5.0-beta.5) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.6)](https://docs.rs/actix-web-codegen/0.5.0-beta.6) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 60b030ff5367e3fab25d08e816671b77885a9426 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:34:23 +0000 Subject: [PATCH 169/861] prepare actix-web-actors release 4.0.0-beta.8 --- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 898098ed8..d3078499c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.8 - 2021-12-11 * Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] * Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 26388fcc3..128d68c15 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.7" +version = "4.0.0-beta.8" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 2c29dedf2..954f8273b 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web-actors/4.0.0-beta.8) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 5b0a50249b047152f05627b1d2f581a775d7ce8b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 00:35:26 +0000 Subject: [PATCH 170/861] prepare actix-multipart release 0.4.0-beta.10 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index d9ded57a4..8d9c1640f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.10 - 2021-12-11 +* No significant changes since `0.4.0-beta.9`. + + ## 0.4.0-beta.9 - 2021-12-01 * Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 9704c255f..6fd1211d9 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.9" +version = "0.4.0-beta.10" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 85c78c5f3..647796557 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.9)](https://docs.rs/actix-multipart/0.4.0-beta.9) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.10)](https://docs.rs/actix-multipart/0.4.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.9/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.9) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.10/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.10) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From b41b346c00260a164731bf484f930d492be61f67 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 16:05:08 +0000 Subject: [PATCH 171/861] inline trivial body methods --- actix-http/src/body/body_stream.rs | 2 ++ actix-http/src/body/either.rs | 7 +++++++ actix-http/src/body/message_body.rs | 24 ++++++++++++++++++++++-- actix-http/src/body/size.rs | 11 ++++++++--- actix-http/src/body/sized_stream.rs | 2 ++ src/error/mod.rs | 1 + 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index 232d01590..cf4f488b2 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -27,6 +27,7 @@ where S: Stream>, E: Into> + 'static, { + #[inline] pub fn new(stream: S) -> Self { BodyStream { stream } } @@ -39,6 +40,7 @@ where { type Error = E; + #[inline] fn size(&self) -> BodySize { BodySize::Stream } diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index 6135d834d..103b39c5d 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -23,6 +23,7 @@ pin_project! { impl EitherBody { /// Creates new `EitherBody` using left variant and boxed right variant. + #[inline] pub fn new(body: L) -> Self { Self::Left { body } } @@ -30,11 +31,13 @@ impl EitherBody { impl EitherBody { /// Creates new `EitherBody` using left variant. + #[inline] pub fn left(body: L) -> Self { Self::Left { body } } /// Creates new `EitherBody` using right variant. + #[inline] pub fn right(body: R) -> Self { Self::Right { body } } @@ -47,6 +50,7 @@ where { type Error = Error; + #[inline] fn size(&self) -> BodySize { match self { EitherBody::Left { body } => body.size(), @@ -54,6 +58,7 @@ where } } + #[inline] fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -68,6 +73,7 @@ where } } + #[inline] fn is_complete_body(&self) -> bool { match self { EitherBody::Left { body } => body.is_complete_body(), @@ -75,6 +81,7 @@ where } } + #[inline] fn take_complete_body(&mut self) -> Bytes { match self { EitherBody::Left { body } => body.take_complete_body(), diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index e4020d2af..3e6c8d5cb 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -85,12 +85,10 @@ mod foreign_impls { impl MessageBody for Infallible { type Error = Infallible; - #[inline] fn size(&self) -> BodySize { match *self {} } - #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -219,6 +217,7 @@ mod foreign_impls { impl MessageBody for &'static [u8] { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -234,10 +233,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { Bytes::from_static(mem::take(self)) } @@ -246,6 +247,7 @@ mod foreign_impls { impl MessageBody for Bytes { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -261,10 +263,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { mem::take(self) } @@ -273,6 +277,7 @@ mod foreign_impls { impl MessageBody for BytesMut { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -288,10 +293,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { mem::take(self).freeze() } @@ -300,6 +307,7 @@ mod foreign_impls { impl MessageBody for Vec { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -315,10 +323,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { Bytes::from(mem::take(self)) } @@ -327,6 +337,7 @@ mod foreign_impls { impl MessageBody for &'static str { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -344,10 +355,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { Bytes::from_static(mem::take(self).as_bytes()) } @@ -356,6 +369,7 @@ mod foreign_impls { impl MessageBody for String { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -372,10 +386,12 @@ mod foreign_impls { } } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { Bytes::from(mem::take(self)) } @@ -384,6 +400,7 @@ mod foreign_impls { impl MessageBody for bytestring::ByteString { type Error = Infallible; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.len() as u64) } @@ -396,10 +413,12 @@ mod foreign_impls { Poll::Ready(Some(Ok(string.into_bytes()))) } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { mem::take(self).into_bytes() } @@ -435,6 +454,7 @@ where { type Error = E; + #[inline] fn size(&self) -> BodySize { self.body.size() } diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs index d64af9d44..ec7873ca5 100644 --- a/actix-http/src/body/size.rs +++ b/actix-http/src/body/size.rs @@ -1,9 +1,11 @@ /// Body size hint. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BodySize { - /// Absence of body can be assumed from method or status code. + /// Implicitly empty body. /// - /// Will skip writing Content-Length header. + /// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or + /// with particular status codes (e.g., 204 No Content). Consumers that read this as a body size + /// hint are allowed to make optimizations that skip reading or writing the payload. None, /// Known size body. @@ -18,6 +20,9 @@ pub enum BodySize { } impl BodySize { + /// Equivalent to `BodySize::Sized(0)`; + pub const ZERO: Self = Self::Sized(0); + /// Returns true if size hint indicates omitted or empty body. /// /// Streams will return false because it cannot be known without reading the stream. diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index c8606897d..9c1727246 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -27,6 +27,7 @@ where S: Stream>, E: Into> + 'static, { + #[inline] pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } } @@ -41,6 +42,7 @@ where { type Error = E; + #[inline] fn size(&self) -> BodySize { BodySize::Sized(self.size as u64) } diff --git a/src/error/mod.rs b/src/error/mod.rs index 4877358a4..64df9f553 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,4 +1,5 @@ //! Error and Result module + // This is meant to be a glob import of the whole error module except for `Error`. Rustdoc can't yet // correctly resolve the conflicting `Error` type defined in this module, so these re-exports are // expanded manually. From cea44be67059a5270a892566323e9055809676de Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Dec 2021 16:18:28 +0000 Subject: [PATCH 172/861] add test for returning App from function --- src/app.rs | 21 +++++++++++++++++++++ tests/test_server.rs | 13 +++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/app.rs b/src/app.rs index ab2081c18..5323cb33a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -706,4 +706,25 @@ mod tests { let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } + + /// compile-only test for returning app type from function + pub fn foreign_app_type() -> App< + impl ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Config = (), + InitError = (), + Error = Error, + >, + > { + App::new() + // logger can be removed without affecting the return type + .wrap(crate::middleware::Logger::default()) + .route("/", web::to(|| async { "hello" })) + } + + #[test] + fn return_foreign_app_type() { + let _app = foreign_app_type(); + } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 51a78eb28..9b7ef6e1b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,8 +10,13 @@ use std::{ task::{Context, Poll}, }; -use actix_http::header::{ - ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, +use actix_web::{ + dev::BodyEncoding, + http::header::{ + ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, + }, + middleware::{Compress, NormalizePath, TrailingSlash}, + web, App, Error, HttpResponse, }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; @@ -31,10 +36,6 @@ use openssl::{ use rand::{distributions::Alphanumeric, Rng}; use zstd::stream::{read::Decoder as ZstdDecoder, write::Encoder as ZstdEncoder}; -use actix_web::dev::BodyEncoding; -use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; -use actix_web::{web, App, Error, HttpResponse}; - const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ From 551a0d973cf50ff9a22665b0ca7b7f195b997ebf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Dec 2021 02:58:19 +0000 Subject: [PATCH 173/861] doc tweaks --- actix-http/src/payload.rs | 4 ++-- src/app_service.rs | 1 + src/request.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 85bfc0b5a..5734af341 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -7,10 +7,10 @@ use h2::RecvStream; use crate::error::PayloadError; -/// Type represent boxed payload +/// A boxed payload. pub type PayloadStream = Pin>>>; -/// Type represent streaming payload +/// A streaming payload. pub enum Payload { None, H1(crate::h1::Payload), diff --git a/src/app_service.rs b/src/app_service.rs index cc5100f04..515693db4 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -22,6 +22,7 @@ use crate::{ type Guards = Vec>; /// Service factory to convert `Request` to a `ServiceRequest`. +/// /// It also executes data factories. pub struct AppInit where diff --git a/src/request.rs b/src/request.rs index d84722d95..07fb4eb2d 100644 --- a/src/request.rs +++ b/src/request.rs @@ -349,7 +349,7 @@ impl Drop for HttpRequest { fn drop(&mut self) { // if possible, contribute to current worker's HttpRequest allocation pool - // This relies on no Weak exists anywhere. (There is none.) + // This relies on no weak references to inner existing anywhere within the codebase. if let Some(inner) = Rc::get_mut(&mut self.inner) { if inner.app_state.pool().is_available() { // clear additional app_data and keep the root one for reuse. @@ -360,7 +360,7 @@ impl Drop for HttpRequest { Rc::get_mut(&mut inner.req_data).unwrap().get_mut().clear(); // a re-borrow of pool is necessary here. - let req = self.inner.clone(); + let req = Rc::clone(&self.inner); self.app_state().pool().push(req); } } From 11ee8ec3ab22da345b448c4cf2d8e51781815873 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Dec 2021 16:08:08 +0000 Subject: [PATCH 174/861] align remaining header map terminology (#2510) --- CHANGES.md | 11 + actix-files/src/named.rs | 62 ++--- actix-http/CHANGES.md | 12 +- actix-http/src/h2/service.rs | 15 +- actix-http/src/header/as_name.rs | 5 + actix-http/src/header/into_pair.rs | 56 ++-- actix-http/src/header/into_value.rs | 30 +-- actix-http/src/header/map.rs | 4 +- actix-http/src/header/mod.rs | 6 +- .../src/header/shared/content_encoding.rs | 4 +- actix-http/src/header/shared/http_date.rs | 5 +- actix-http/src/response.rs | 2 +- actix-http/src/response_builder.rs | 20 +- actix-http/src/test.rs | 16 +- awc/CHANGES.md | 5 +- awc/src/builder.rs | 83 +++--- awc/src/client/h1proto.rs | 2 +- awc/src/frozen.rs | 6 +- awc/src/lib.rs | 10 +- awc/src/middleware/redirect.rs | 8 +- awc/src/request.rs | 27 +- awc/src/sender.rs | 4 +- awc/src/test.rs | 21 +- awc/src/ws.rs | 10 +- examples/basic.rs | 4 +- examples/uds.rs | 4 +- src/app.rs | 4 +- src/error/internal.rs | 2 +- src/error/response_error.rs | 2 +- src/http/header/content_disposition.rs | 4 +- src/http/header/content_range.rs | 4 +- src/http/header/entity.rs | 4 +- src/http/header/if_range.rs | 6 +- src/http/header/macros.rs | 8 +- src/http/header/range.rs | 4 +- src/lib.rs | 7 +- src/middleware/default_headers.rs | 127 +++++---- src/middleware/mod.rs | 4 +- src/request_data.rs | 2 +- src/resource.rs | 5 +- src/response/builder.rs | 24 +- src/response/customize_responder.rs | 245 ++++++++++++++++++ src/response/mod.rs | 4 + src/{ => response}/responder.rs | 205 ++------------- src/scope.rs | 2 +- src/test.rs | 14 +- src/web.rs | 2 +- 47 files changed, 608 insertions(+), 503 deletions(-) create mode 100644 src/response/customize_responder.rs rename src/{ => response}/responder.rs (63%) diff --git a/CHANGES.md b/CHANGES.md index 3e0b12d9e..2df820027 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] +* Implement `Debug` for `DefaultHeaders`. [#2510] + +### Changed +* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] + +### Removed +* Top-level `EitherExtractError` export. [#2510] + +[#2510]: https://github.com/actix/actix-web/pull/2510 ## 4.0.0-beta.14 - 2021-12-11 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 0848543a8..810988f0c 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -2,14 +2,10 @@ use std::{ fmt, fs::Metadata, io, - ops::{Deref, DerefMut}, path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, }; -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - use actix_service::{Service, ServiceFactory}; use actix_web::{ body::{self, BoxBody, SizedStream}, @@ -27,6 +23,7 @@ use actix_web::{ Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; +use derive_more::{Deref, DerefMut}; use futures_core::future::LocalBoxFuture; use mime_guess::from_path; @@ -71,8 +68,11 @@ impl Default for Flags { /// NamedFile::open_async("./static/index.html").await /// } /// ``` +#[derive(Deref, DerefMut)] pub struct NamedFile { path: PathBuf, + #[deref] + #[deref_mut] file: File, modified: Option, pub(crate) md: Metadata, @@ -364,14 +364,18 @@ impl NamedFile { self } + /// Creates a etag in a format is similar to Apache's. pub(crate) fn etag(&self) -> Option { - // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { let ino = { #[cfg(unix)] { + #[cfg(unix)] + use std::os::unix::fs::MetadataExt as _; + self.md.ino() } + #[cfg(not(unix))] { 0 @@ -472,17 +476,17 @@ impl NamedFile { false }; - let mut resp = HttpResponse::build(self.status_code); + let mut res = HttpResponse::build(self.status_code); if self.flags.contains(Flags::PREFER_UTF8) { let ct = equiv_utf8_text(self.content_type.clone()); - resp.insert_header((header::CONTENT_TYPE, ct.to_string())); + res.insert_header((header::CONTENT_TYPE, ct.to_string())); } else { - resp.insert_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) { - resp.insert_header(( + res.insert_header(( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), )); @@ -490,18 +494,18 @@ impl NamedFile { // default compressing if let Some(current_encoding) = self.encoding { - resp.encoding(current_encoding); + res.encoding(current_encoding); } if let Some(lm) = last_modified { - resp.insert_header((header::LAST_MODIFIED, lm.to_string())); + res.insert_header((header::LAST_MODIFIED, lm.to_string())); } if let Some(etag) = etag { - resp.insert_header((header::ETAG, etag.to_string())); + res.insert_header((header::ETAG, etag.to_string())); } - resp.insert_header((header::ACCEPT_RANGES, "bytes")); + res.insert_header((header::ACCEPT_RANGES, "bytes")); let mut length = self.md.len(); let mut offset = 0; @@ -513,24 +517,24 @@ impl NamedFile { length = ranges[0].length; offset = ranges[0].start; - resp.encoding(ContentEncoding::Identity); - resp.insert_header(( + res.encoding(ContentEncoding::Identity); + res.insert_header(( header::CONTENT_RANGE, format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), )); } else { - resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length))); - return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); + res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length))); + return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { - return resp.status(StatusCode::BAD_REQUEST).finish(); + return res.status(StatusCode::BAD_REQUEST).finish(); }; }; if precondition_failed { - return resp.status(StatusCode::PRECONDITION_FAILED).finish(); + return res.status(StatusCode::PRECONDITION_FAILED).finish(); } else if not_modified { - return resp + return res .status(StatusCode::NOT_MODIFIED) .body(body::None::new()) .map_into_boxed_body(); @@ -539,10 +543,10 @@ impl NamedFile { let reader = chunked::new_chunked_read(length, offset, self.file); if offset != 0 || length != self.md.len() { - resp.status(StatusCode::PARTIAL_CONTENT); + res.status(StatusCode::PARTIAL_CONTENT); } - resp.body(SizedStream::new(length, reader)) + res.body(SizedStream::new(length, reader)) } } @@ -586,20 +590,6 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &Self::Target { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.file - } -} - impl Responder for NamedFile { type Body = BoxBody; diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index fe47902f7..011e2c608 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] + +[#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.15 - 2021-12-11 @@ -260,7 +266,7 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +* `TryIntoHeaderPair` 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] @@ -271,9 +277,9 @@ * `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed +* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] * `Extensions::insert` returns Option of replaced item. [#1904] * Remove `HttpResponseBuilder::json2()`. [#1903] diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index f5821370a..469648054 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -270,10 +270,10 @@ where type Future = H2ServiceHandlerResponse; 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) + self.flow.service.poll_ready(cx).map_err(|err| { + let err = err.into(); + error!("Service readiness error: {:?}", err); + DispatchError::Service(err) }) } @@ -297,7 +297,6 @@ where T: AsyncRead + AsyncWrite + Unpin, S::Future: 'static, { - Incoming(Dispatcher), Handshake( Option>>, Option, @@ -305,6 +304,7 @@ where OnConnectData, HandshakeWithTimeout, ), + Established(Dispatcher), } pub struct H2ServiceHandlerResponse @@ -332,7 +332,6 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.state { - State::Incoming(ref mut disp) => Pin::new(disp).poll(cx), State::Handshake( ref mut srv, ref mut config, @@ -343,7 +342,7 @@ where Ok((conn, timer)) => { let on_connect_data = mem::take(conn_data); - self.state = State::Incoming(Dispatcher::new( + self.state = State::Established(Dispatcher::new( conn, srv.take().unwrap(), config.take().unwrap(), @@ -360,6 +359,8 @@ where Poll::Ready(Err(err)) } }, + + State::Established(ref mut disp) => Pin::new(disp).poll(cx), } } } diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index 17d007f2f..a895010b1 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -16,6 +16,7 @@ pub trait Sealed { } impl Sealed for HeaderName { + #[inline] fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { Ok(Cow::Borrowed(self)) } @@ -23,6 +24,7 @@ impl Sealed for HeaderName { impl AsHeaderName for HeaderName {} impl Sealed for &HeaderName { + #[inline] fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { Ok(Cow::Borrowed(*self)) } @@ -30,6 +32,7 @@ impl Sealed for &HeaderName { impl AsHeaderName for &HeaderName {} impl Sealed for &str { + #[inline] fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } @@ -37,6 +40,7 @@ impl Sealed for &str { impl AsHeaderName for &str {} impl Sealed for String { + #[inline] fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } @@ -44,6 +48,7 @@ impl Sealed for String { impl AsHeaderName for String {} impl Sealed for &String { + #[inline] fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { HeaderName::from_str(self).map(Cow::Owned) } diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs index b4250e06e..91c3e6640 100644 --- a/actix-http/src/header/into_pair.rs +++ b/actix-http/src/header/into_pair.rs @@ -1,22 +1,20 @@ -//! [`IntoHeaderPair`] trait and implementations. +//! [`TryIntoHeaderPair`] trait and implementations. use std::convert::TryFrom as _; -use http::{ - header::{HeaderName, InvalidHeaderName, InvalidHeaderValue}, - Error as HttpError, HeaderValue, +use super::{ + Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue, }; +use crate::error::HttpError; -use super::{Header, IntoHeaderValue}; - -/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for +/// An interface for types that can be converted into a [`HeaderName`] + [`HeaderValue`] pair for /// insertion into a [`HeaderMap`]. /// /// [`HeaderMap`]: super::HeaderMap -pub trait IntoHeaderPair: Sized { +pub trait TryIntoHeaderPair: Sized { type Error: Into; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>; + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>; } #[derive(Debug)] @@ -34,14 +32,14 @@ impl From for HttpError { } } -impl IntoHeaderPair for (HeaderName, V) +impl TryIntoHeaderPair for (HeaderName, V) where - V: IntoHeaderValue, + V: TryIntoHeaderValue, V::Error: Into, { type Error = InvalidHeaderPart; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { let (name, value) = self; let value = value .try_into_value() @@ -50,14 +48,14 @@ where } } -impl IntoHeaderPair for (&HeaderName, V) +impl TryIntoHeaderPair for (&HeaderName, V) where - V: IntoHeaderValue, + V: TryIntoHeaderValue, V::Error: Into, { type Error = InvalidHeaderPart; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { let (name, value) = self; let value = value .try_into_value() @@ -66,14 +64,14 @@ where } } -impl IntoHeaderPair for (&[u8], V) +impl TryIntoHeaderPair for (&[u8], V) where - V: IntoHeaderValue, + V: TryIntoHeaderValue, V::Error: Into, { type Error = InvalidHeaderPart; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { let (name, value) = self; let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; let value = value @@ -83,14 +81,14 @@ where } } -impl IntoHeaderPair for (&str, V) +impl TryIntoHeaderPair for (&str, V) where - V: IntoHeaderValue, + V: TryIntoHeaderValue, V::Error: Into, { type Error = InvalidHeaderPart; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { let (name, value) = self; let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; let value = value @@ -100,23 +98,25 @@ where } } -impl IntoHeaderPair for (String, V) +impl TryIntoHeaderPair for (String, V) where - V: IntoHeaderValue, + V: TryIntoHeaderValue, V::Error: Into, { type Error = InvalidHeaderPart; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + #[inline] + fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { let (name, value) = self; - (name.as_str(), value).try_into_header_pair() + (name.as_str(), value).try_into_pair() } } -impl IntoHeaderPair for T { - type Error = ::Error; +impl TryIntoHeaderPair for T { + type Error = ::Error; - fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + #[inline] + fn try_into_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 index bad05db64..6d369ee65 100644 --- a/actix-http/src/header/into_value.rs +++ b/actix-http/src/header/into_value.rs @@ -1,4 +1,4 @@ -//! [`IntoHeaderValue`] trait and implementations. +//! [`TryIntoHeaderValue`] trait and implementations. use std::convert::TryFrom as _; @@ -7,7 +7,7 @@ use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue}; use mime::Mime; /// An interface for types that can be converted into a [`HeaderValue`]. -pub trait IntoHeaderValue: Sized { +pub trait TryIntoHeaderValue: Sized { /// The type returned in the event of a conversion error. type Error: Into; @@ -15,7 +15,7 @@ pub trait IntoHeaderValue: Sized { fn try_into_value(self) -> Result; } -impl IntoHeaderValue for HeaderValue { +impl TryIntoHeaderValue for HeaderValue { type Error = InvalidHeaderValue; #[inline] @@ -24,7 +24,7 @@ impl IntoHeaderValue for HeaderValue { } } -impl IntoHeaderValue for &HeaderValue { +impl TryIntoHeaderValue for &HeaderValue { type Error = InvalidHeaderValue; #[inline] @@ -33,7 +33,7 @@ impl IntoHeaderValue for &HeaderValue { } } -impl IntoHeaderValue for &str { +impl TryIntoHeaderValue for &str { type Error = InvalidHeaderValue; #[inline] @@ -42,7 +42,7 @@ impl IntoHeaderValue for &str { } } -impl IntoHeaderValue for &[u8] { +impl TryIntoHeaderValue for &[u8] { type Error = InvalidHeaderValue; #[inline] @@ -51,7 +51,7 @@ impl IntoHeaderValue for &[u8] { } } -impl IntoHeaderValue for Bytes { +impl TryIntoHeaderValue for Bytes { type Error = InvalidHeaderValue; #[inline] @@ -60,7 +60,7 @@ impl IntoHeaderValue for Bytes { } } -impl IntoHeaderValue for Vec { +impl TryIntoHeaderValue for Vec { type Error = InvalidHeaderValue; #[inline] @@ -69,7 +69,7 @@ impl IntoHeaderValue for Vec { } } -impl IntoHeaderValue for String { +impl TryIntoHeaderValue for String { type Error = InvalidHeaderValue; #[inline] @@ -78,7 +78,7 @@ impl IntoHeaderValue for String { } } -impl IntoHeaderValue for usize { +impl TryIntoHeaderValue for usize { type Error = InvalidHeaderValue; #[inline] @@ -87,7 +87,7 @@ impl IntoHeaderValue for usize { } } -impl IntoHeaderValue for i64 { +impl TryIntoHeaderValue for i64 { type Error = InvalidHeaderValue; #[inline] @@ -96,7 +96,7 @@ impl IntoHeaderValue for i64 { } } -impl IntoHeaderValue for u64 { +impl TryIntoHeaderValue for u64 { type Error = InvalidHeaderValue; #[inline] @@ -105,7 +105,7 @@ impl IntoHeaderValue for u64 { } } -impl IntoHeaderValue for i32 { +impl TryIntoHeaderValue for i32 { type Error = InvalidHeaderValue; #[inline] @@ -114,7 +114,7 @@ impl IntoHeaderValue for i32 { } } -impl IntoHeaderValue for u32 { +impl TryIntoHeaderValue for u32 { type Error = InvalidHeaderValue; #[inline] @@ -123,7 +123,7 @@ impl IntoHeaderValue for u32 { } } -impl IntoHeaderValue for Mime { +impl TryIntoHeaderValue for Mime { type Error = InvalidHeaderValue; #[inline] diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 12c8f9462..748410375 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -333,7 +333,7 @@ impl HeaderMap { } } - /// Inserts a name-value pair into the map. + /// Inserts (overrides) a name-value pair in the map. /// /// If the map already contained this key, the new value is associated with the key and all /// previous values are removed and returned as a `Removed` iterator. The key is not updated; @@ -372,7 +372,7 @@ impl HeaderMap { Removed::new(value) } - /// Inserts a name-value pair into the map. + /// Appends a name-value pair to the map. /// /// If the map already contained this key, the new value is added to the list of values /// currently associated with the key. The key is not updated; this matters for types that can diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 5fe76381b..dd4f06106 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -37,8 +37,8 @@ mod shared; mod utils; pub use self::as_name::AsHeaderName; -pub use self::into_pair::IntoHeaderPair; -pub use self::into_value::IntoHeaderValue; +pub use self::into_pair::TryIntoHeaderPair; +pub use self::into_value::TryIntoHeaderValue; pub use self::map::HeaderMap; pub use self::shared::{ parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag, @@ -49,7 +49,7 @@ pub use self::utils::{ }; /// An interface for types that already represent a valid header. -pub trait Header: IntoHeaderValue { +pub trait Header: TryIntoHeaderValue { /// Returns the name of the header field fn name() -> HeaderName; diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index 073d90dce..a6e52138d 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -5,7 +5,7 @@ use http::header::InvalidHeaderValue; use crate::{ error::ParseError, - header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue}, + header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue}, HttpMessage, }; @@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding { } } -impl IntoHeaderValue for ContentEncoding { +impl TryIntoHeaderValue for ContentEncoding { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 228f6f00e..473d6cad0 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -4,7 +4,8 @@ use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use crate::{ - config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter, + config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, + helpers::MutWriter, }; /// A timestamp with HTTP-style formatting and parsing. @@ -29,7 +30,7 @@ impl fmt::Display for HttpDate { } } -impl IntoHeaderValue for HttpDate { +impl TryIntoHeaderValue for HttpDate { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 861cab2cb..9f799f669 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -11,7 +11,7 @@ use bytestring::ByteString; use crate::{ body::{BoxBody, MessageBody}, extensions::Extensions, - header::{self, HeaderMap, IntoHeaderValue}, + header::{self, HeaderMap, TryIntoHeaderValue}, message::{BoxedResponseHead, ResponseHead}, Error, ResponseBuilder, StatusCode, }; diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs index dfc2612fb..adbe86fca 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/response_builder.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ body::{EitherBody, MessageBody}, error::{Error, HttpError}, - header::{self, IntoHeaderPair, IntoHeaderValue}, + header::{self, TryIntoHeaderPair, TryIntoHeaderValue}, message::{BoxedResponseHead, ConnectionType, ResponseHead}, Extensions, Response, StatusCode, }; @@ -90,12 +90,9 @@ impl ResponseBuilder { /// assert!(res.headers().contains_key("content-type")); /// assert!(res.headers().contains_key("x-test")); /// ``` - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { + pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { if let Some(parts) = self.inner() { - match header.try_into_header_pair() { + match header.try_into_pair() { Ok((key, value)) => { parts.headers.insert(key, value); } @@ -121,12 +118,9 @@ impl ResponseBuilder { /// assert_eq!(res.headers().get_all("content-type").count(), 1); /// assert_eq!(res.headers().get_all("x-test").count(), 2); /// ``` - pub fn append_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { + pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { if let Some(parts) = self.inner() { - match header.try_into_header_pair() { + match header.try_into_pair() { Ok((key, value)) => parts.headers.append(key, value), Err(e) => self.err = Some(e.into()), }; @@ -157,7 +151,7 @@ impl ResponseBuilder { #[inline] pub fn upgrade(&mut self, value: V) -> &mut Self where - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Upgrade); @@ -195,7 +189,7 @@ impl ResponseBuilder { #[inline] pub fn content_type(&mut self, value: V) -> &mut Self where - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if let Some(parts) = self.inner() { match value.try_into_value() { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ec781743d..7e26ee865 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -14,7 +14,7 @@ use bytes::{Bytes, BytesMut}; use http::{Method, Uri, Version}; use crate::{ - header::{HeaderMap, IntoHeaderPair}, + header::{HeaderMap, TryIntoHeaderPair}, payload::Payload, Request, }; @@ -92,11 +92,8 @@ impl TestRequest { } /// Insert a header, replacing any that were set with an equivalent field name. - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - match header.try_into_header_pair() { + pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { + match header.try_into_pair() { Ok((key, value)) => { parts(&mut self.0).headers.insert(key, value); } @@ -109,11 +106,8 @@ impl TestRequest { } /// 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() { + pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { + match header.try_into_pair() { Ok((key, value)) => { parts(&mut self.0).headers.append(key, value); } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 798b2ce6b..8a3fea46a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] + +[#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 @@ -60,7 +63,7 @@ * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * Fix http/https encoding when enabling `compress` feature. [#2116] * Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header - methods now take `IntoHeaderPair` tuples. [#2094] + methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 [#2094]: https://github.com/actix/actix-web/pull/2094 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 43e5c0def..30f203bb8 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -2,7 +2,7 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration}; use actix_http::{ error::HttpError, - header::{self, HeaderMap, HeaderName}, + header::{self, HeaderMap, HeaderName, TryIntoHeaderPair}, Uri, }; use actix_rt::net::{ActixStream, TcpStream}; @@ -21,11 +21,11 @@ use crate::{ /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. pub struct ClientBuilder { - default_headers: bool, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, - headers: HeaderMap, + fundamental_headers: bool, + default_headers: HeaderMap, timeout: Option, connector: Connector, middleware: M, @@ -44,15 +44,15 @@ impl ClientBuilder { (), > { ClientBuilder { - middleware: (), - default_headers: true, - headers: HeaderMap::new(), - timeout: Some(Duration::from_secs(5)), - local_address: None, - connector: Connector::new(), max_http_version: None, stream_window_size: None, conn_window_size: None, + fundamental_headers: true, + default_headers: HeaderMap::new(), + timeout: Some(Duration::from_secs(5)), + connector: Connector::new(), + middleware: (), + local_address: None, max_redirects: 10, } } @@ -78,8 +78,8 @@ where { ClientBuilder { middleware: self.middleware, + fundamental_headers: self.fundamental_headers, default_headers: self.default_headers, - headers: self.headers, timeout: self.timeout, local_address: self.local_address, connector, @@ -153,30 +153,46 @@ where self } - /// Do not add default request headers. + /// Do not add fundamental default request headers. + /// /// By default `Date` and `User-Agent` headers are set. pub fn no_default_headers(mut self) -> Self { - self.default_headers = false; + self.fundamental_headers = false; self } - /// Add default header. Headers added by this method - /// get added to every request. + /// Add default header. + /// + /// Headers added by this method get added to every request unless overriden by . + /// + /// # Panics + /// Panics if header name or value is invalid. + pub fn add_default_header(mut self, header: impl TryIntoHeaderPair) -> Self { + match header.try_into_pair() { + Ok((key, value)) => self.default_headers.append(key, value), + Err(err) => panic!("Header error: {:?}", err.into()), + } + + self + } + + #[doc(hidden)] + #[deprecated(since = "3.0.0", note = "Prefer `add_default_header((key, value))`.")] pub fn header(mut self, key: K, value: V) -> Self where HeaderName: TryFrom, >::Error: fmt::Debug + Into, - V: header::IntoHeaderValue, + V: header::TryIntoHeaderValue, V::Error: fmt::Debug, { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { Ok(value) => { - self.headers.append(key, value); + self.default_headers.append(key, value); } - Err(e) => log::error!("Header value error: {:?}", e), + Err(err) => log::error!("Header value error: {:?}", err), }, - Err(e) => log::error!("Header name error: {:?}", e), + Err(err) => log::error!("Header name error: {:?}", err), } self } @@ -190,10 +206,10 @@ where Some(password) => format!("{}:{}", username, password), None => format!("{}:", username), }; - self.header( + self.add_default_header(( header::AUTHORIZATION, format!("Basic {}", base64::encode(&auth)), - ) + )) } /// Set client wide HTTP bearer authentication header @@ -201,13 +217,12 @@ where where T: fmt::Display, { - self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + self.add_default_header((header::AUTHORIZATION, format!("Bearer {}", token))) } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// life-cycle (request -> response), modifying request/response as - /// necessary, across all requests managed by the Client. + /// Registers middleware, in the form of a middleware component (type), that runs during inbound + /// and/or outbound processing in the request life-cycle (request -> response), + /// modifying request/response as necessary, across all requests managed by the `Client`. pub fn wrap( self, mw: M1, @@ -218,11 +233,11 @@ where { ClientBuilder { middleware: NestTransform::new(self.middleware, mw), - default_headers: self.default_headers, + fundamental_headers: self.fundamental_headers, max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, conn_window_size: self.conn_window_size, - headers: self.headers, + default_headers: self.default_headers, timeout: self.timeout, connector: self.connector, local_address: self.local_address, @@ -237,10 +252,10 @@ where M::Transform: Service, { - let redirect_time = self.max_redirects; + let max_redirects = self.max_redirects; - if redirect_time > 0 { - self.wrap(Redirect::new().max_redirect_times(redirect_time)) + if max_redirects > 0 { + self.wrap(Redirect::new().max_redirect_times(max_redirects)) ._finish() } else { self._finish() @@ -272,7 +287,7 @@ where let connector = boxed::rc_service(self.middleware.new_transform(connector)); Client(ClientConfig { - headers: Rc::new(self.headers), + default_headers: Rc::new(self.default_headers), timeout: self.timeout, connector, }) @@ -288,7 +303,7 @@ mod tests { let client = ClientBuilder::new().basic_auth("username", Some("password")); assert_eq!( client - .headers + .default_headers .get(header::AUTHORIZATION) .unwrap() .to_str() @@ -299,7 +314,7 @@ mod tests { let client = ClientBuilder::new().basic_auth("username", None); assert_eq!( client - .headers + .default_headers .get(header::AUTHORIZATION) .unwrap() .to_str() @@ -313,7 +328,7 @@ mod tests { let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( client - .headers + .default_headers .get(header::AUTHORIZATION) .unwrap() .to_str() diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index c8b9a3fae..1028a2178 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -9,7 +9,7 @@ use actix_http::{ body::{BodySize, MessageBody}, error::PayloadError, h1, - header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, + header::{HeaderMap, TryIntoHeaderValue, EXPECT, HOST}, Payload, RequestHeadType, ResponseHead, StatusCode, }; use actix_utils::future::poll_fn; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 7497f85c8..cd93a1d60 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -6,7 +6,7 @@ use serde::Serialize; use actix_http::{ error::HttpError, - header::{HeaderMap, HeaderName, IntoHeaderValue}, + header::{HeaderMap, HeaderName, TryIntoHeaderValue}, Method, RequestHead, Uri, }; @@ -114,7 +114,7 @@ impl FrozenClientRequest { where HeaderName: TryFrom, >::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { self.extra_headers(HeaderMap::new()) .extra_header(key, value) @@ -142,7 +142,7 @@ impl FrozenSendBuilder { where HeaderName: TryFrom, >::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 06fd33fac..00c559406 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -168,7 +168,7 @@ pub struct Client(ClientConfig); #[derive(Clone)] pub(crate) struct ClientConfig { pub(crate) connector: BoxConnectorService, - pub(crate) headers: Rc, + pub(crate) default_headers: Rc, pub(crate) timeout: Option, } @@ -204,7 +204,9 @@ impl Client { { let mut req = ClientRequest::new(method, url, self.0.clone()); - for header in self.0.headers.iter() { + for header in self.0.default_headers.iter() { + // header map is empty + // TODO: probably append instead req = req.insert_header_if_none(header); } req @@ -297,7 +299,7 @@ impl Client { >::Error: Into, { let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in self.0.headers.iter() { + for (key, value) in self.0.default_headers.iter() { req.head.headers.insert(key.clone(), value.clone()); } req @@ -308,6 +310,6 @@ impl Client { /// Returns Some(&mut HeaderMap) when Client object is unique /// (No other clone of client exists at the same time). pub fn headers(&mut self) -> Option<&mut HeaderMap> { - Rc::get_mut(&mut self.0.headers) + Rc::get_mut(&mut self.0.default_headers) } } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 0fde48074..704d2d79d 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -442,13 +442,15 @@ mod tests { }); let client = ClientBuilder::new() - .header("custom", "value") + .add_default_header(("custom", "value")) .disable_redirects() .finish(); let res = client.get(srv.url("/")).send().await.unwrap(); assert_eq!(res.status().as_u16(), 302); - let client = ClientBuilder::new().header("custom", "value").finish(); + let client = ClientBuilder::new() + .add_default_header(("custom", "value")) + .finish(); let res = client.get(srv.url("/")).send().await.unwrap(); assert_eq!(res.status().as_u16(), 200); @@ -520,7 +522,7 @@ mod tests { // send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header let client = ClientBuilder::new() - .header(header::AUTHORIZATION, "auth_key_value") + .add_default_header((header::AUTHORIZATION, "auth_key_value")) .finish(); let res = client.get(srv1.url("/")).send().await.unwrap(); assert_eq!(res.status().as_u16(), 200); diff --git a/awc/src/request.rs b/awc/src/request.rs index 3e1f83a82..9e37b2755 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -6,7 +6,7 @@ use serde::Serialize; use actix_http::{ error::HttpError, - header::{self, HeaderMap, HeaderValue, IntoHeaderPair}, + header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair}, ConnectionType, Method, RequestHead, Uri, Version, }; @@ -147,11 +147,8 @@ impl ClientRequest { } /// Insert a header, replacing any that were set with an equivalent field name. - pub fn insert_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - match header.try_into_header_pair() { + pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { + match header.try_into_pair() { Ok((key, value)) => { self.head.headers.insert(key, value); } @@ -162,11 +159,8 @@ impl ClientRequest { } /// Insert a header only if it is not yet set. - pub fn insert_header_if_none(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - match header.try_into_header_pair() { + pub fn insert_header_if_none(mut self, header: impl TryIntoHeaderPair) -> Self { + match header.try_into_pair() { Ok((key, value)) => { if !self.head.headers.contains_key(&key) { self.head.headers.insert(key, value); @@ -192,11 +186,8 @@ impl ClientRequest { /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)); /// # } /// ``` - pub fn append_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - match header.try_into_header_pair() { + pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { + match header.try_into_pair() { Ok((key, value)) => self.head.headers.append(key, value), Err(e) => self.err = Some(e.into()), }; @@ -588,7 +579,7 @@ mod tests { #[actix_rt::test] async fn test_client_header() { let req = Client::builder() - .header(header::CONTENT_TYPE, "111") + .add_default_header((header::CONTENT_TYPE, "111")) .finish() .get("/"); @@ -606,7 +597,7 @@ mod tests { #[actix_rt::test] async fn test_client_header_override() { let req = Client::builder() - .header(header::CONTENT_TYPE, "111") + .add_default_header((header::CONTENT_TYPE, "111")) .finish() .get("/") .insert_header((header::CONTENT_TYPE, "222")); diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 1faf6140a..f83a70a9b 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -10,7 +10,7 @@ use std::{ use actix_http::{ body::BodyStream, error::HttpError, - header::{self, HeaderMap, HeaderName, IntoHeaderValue}, + header::{self, HeaderMap, HeaderName, TryIntoHeaderValue}, RequestHead, RequestHeadType, }; use actix_rt::time::{sleep, Sleep}; @@ -298,7 +298,7 @@ impl RequestSender { fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> where - V: IntoHeaderValue, + V: TryIntoHeaderValue, { match self { RequestSender::Owned(head) => { diff --git a/awc/src/test.rs b/awc/src/test.rs index 4a5c8e7ea..1b41efc93 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,6 +1,6 @@ //! Test helpers for actix http client to use during testing. -use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version}; +use actix_http::{h1, header::TryIntoHeaderPair, Payload, ResponseHead, StatusCode, Version}; use bytes::Bytes; #[cfg(feature = "cookies")] @@ -28,10 +28,7 @@ impl Default for TestResponse { impl TestResponse { /// Create TestResponse and set header - pub fn with_header(header: H) -> Self - where - H: IntoHeaderPair, - { + pub fn with_header(header: impl TryIntoHeaderPair) -> Self { Self::default().insert_header(header) } @@ -42,11 +39,8 @@ impl TestResponse { } /// Insert a header - pub fn insert_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - if let Ok((key, value)) = header.try_into_header_pair() { + pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { + if let Ok((key, value)) = header.try_into_pair() { self.head.headers.insert(key, value); return self; } @@ -54,11 +48,8 @@ impl TestResponse { } /// Append a header - pub fn append_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - if let Ok((key, value)) = header.try_into_header_pair() { + pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { + if let Ok((key, value)) = header.try_into_pair() { self.head.headers.append(key, value); return self; } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f0d421dbc..06d54aadb 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -39,7 +39,7 @@ use crate::{ connect::{BoxedSocket, ConnectRequest}, error::{HttpError, InvalidUrl, SendRequestError, WsClientError}, http::{ - header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}, + header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}, ConnectionType, Method, StatusCode, Uri, Version, }, response::ClientResponse, @@ -171,7 +171,7 @@ impl WebsocketsRequest { where HeaderName: TryFrom, >::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { @@ -190,7 +190,7 @@ impl WebsocketsRequest { where HeaderName: TryFrom, >::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { @@ -209,7 +209,7 @@ impl WebsocketsRequest { where HeaderName: TryFrom, >::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { match HeaderName::try_from(key) { Ok(key) => { @@ -445,7 +445,7 @@ mod tests { #[actix_rt::test] async fn test_header_override() { let req = Client::builder() - .header(header::CONTENT_TYPE, "111") + .add_default_header((header::CONTENT_TYPE, "111")) .finish() .ws("/") .set_header(header::CONTENT_TYPE, "222"); diff --git a/examples/basic.rs b/examples/basic.rs index d29546129..598d13a40 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -22,14 +22,14 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() - .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) .wrap(middleware::Compress::default()) .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( web::resource("/resource2/index.html") - .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3"))) .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) diff --git a/examples/uds.rs b/examples/uds.rs index 1db252fef..cf0ffebde 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -26,14 +26,14 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() - .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) .wrap(middleware::Compress::default()) .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( web::resource("/resource2/index.html") - .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3"))) .default_service(web::route().to(HttpResponse::MethodNotAllowed)) .route(web::get().to(index_async)), ) diff --git a/src/app.rs b/src/app.rs index 5323cb33a..feb35d7ae 100644 --- a/src/app.rs +++ b/src/app.rs @@ -602,7 +602,7 @@ mod tests { App::new() .wrap( DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), ) .route("/test", web::get().to(HttpResponse::Ok)), ) @@ -623,7 +623,7 @@ mod tests { .route("/test", web::get().to(HttpResponse::Ok)) .wrap( DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), ), ) .await; diff --git a/src/error/internal.rs b/src/error/internal.rs index b8e169018..37195dc2e 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, fmt, io::Write as _}; use actix_http::{ body::BoxBody, - header::{self, IntoHeaderValue as _}, + header::{self, TryIntoHeaderValue as _}, StatusCode, }; use bytes::{BufMut as _, BytesMut}; diff --git a/src/error/response_error.rs b/src/error/response_error.rs index 7260efa1a..e0b4af44c 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -8,7 +8,7 @@ use std::{ use actix_http::{ body::BoxBody, - header::{self, IntoHeaderValue}, + header::{self, TryIntoHeaderValue}, Response, StatusCode, }; use bytes::BytesMut; diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 945a58f7f..26a9d8e76 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -14,7 +14,7 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; +use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer}; use crate::http::header; /// Split at the index of the first `needle` if it exists or at the end. @@ -454,7 +454,7 @@ impl ContentDisposition { } } -impl IntoHeaderValue for ContentDisposition { +impl TryIntoHeaderValue for ContentDisposition { type Error = header::InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index 90b3f7fe2..bcbe77e66 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -3,7 +3,7 @@ use std::{ str::FromStr, }; -use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; +use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; crate::http::header::common_header! { @@ -196,7 +196,7 @@ impl Display for ContentRangeSpec { } } -impl IntoHeaderValue for ContentRangeSpec { +impl TryIntoHeaderValue for ContentRangeSpec { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/src/http/header/entity.rs b/src/http/header/entity.rs index 50b40b7b2..76fe39f23 100644 --- a/src/http/header/entity.rs +++ b/src/http/header/entity.rs @@ -3,7 +3,7 @@ use std::{ str::FromStr, }; -use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; +use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer}; /// check that each char in the slice is either: /// 1. `%x21`, or @@ -159,7 +159,7 @@ impl FromStr for EntityTag { } } -impl IntoHeaderValue for EntityTag { +impl TryIntoHeaderValue for EntityTag { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 5af9255f6..b845fb3bf 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display, Write}; use super::{ - from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValue, Writer, + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue, + TryIntoHeaderValue, Writer, }; use crate::error::ParseError; use crate::http::header; @@ -96,7 +96,7 @@ impl Display for IfRange { } } -impl IntoHeaderValue for IfRange { +impl TryIntoHeaderValue for IfRange { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index ca3792a37..25f40a52b 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -125,7 +125,7 @@ macro_rules! common_header { } } - impl $crate::http::header::IntoHeaderValue for $id { + impl $crate::http::header::TryIntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; #[inline] @@ -172,7 +172,7 @@ macro_rules! common_header { } } - impl $crate::http::header::IntoHeaderValue for $id { + impl $crate::http::header::TryIntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; #[inline] @@ -211,7 +211,7 @@ macro_rules! common_header { } } - impl $crate::http::header::IntoHeaderValue for $id { + impl $crate::http::header::TryIntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; #[inline] @@ -266,7 +266,7 @@ macro_rules! common_header { } } - impl $crate::http::header::IntoHeaderValue for $id { + impl $crate::http::header::TryIntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; #[inline] diff --git a/src/http/header/range.rs b/src/http/header/range.rs index c1d60f1ee..68028f53a 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -6,7 +6,7 @@ use std::{ use actix_http::{error::ParseError, header, HttpMessage}; -use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; +use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer}; /// `Range` header, defined /// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) @@ -274,7 +274,7 @@ impl Header for Range { } } -impl IntoHeaderValue for Range { +impl TryIntoHeaderValue for Range { type Error = InvalidHeaderValue; fn try_into_value(self) -> Result { diff --git a/src/lib.rs b/src/lib.rs index a44c9b3fb..171a2d101 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,6 @@ pub mod middleware; mod request; mod request_data; mod resource; -mod responder; mod response; mod rmap; mod route; @@ -109,12 +108,10 @@ pub use crate::error::{Error, ResponseError, Result}; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; -pub use crate::responder::Responder; -pub use crate::response::{HttpResponse, HttpResponseBuilder}; +pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder}; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; -// TODO: is exposing the error directly really needed -pub use crate::types::{Either, EitherExtractError}; +pub use crate::types::Either; pub(crate) type BoxError = Box; diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index dceca44c2..257467710 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -16,7 +16,7 @@ use pin_project_lite::pin_project; use crate::{ dev::{Service, Transform}, - http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}, + http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE}, service::{ServiceRequest, ServiceResponse}, Error, }; @@ -29,79 +29,81 @@ use crate::{ /// ``` /// use actix_web::{web, http, middleware, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// let app = App::new() +/// .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) +/// ); /// ``` -#[derive(Clone)] +#[derive(Debug, Clone, Default)] pub struct DefaultHeaders { inner: Rc, } +#[derive(Debug, Default)] struct Inner { headers: HeaderMap, } -impl Default for DefaultHeaders { - fn default() -> Self { - DefaultHeaders { - inner: Rc::new(Inner { - headers: HeaderMap::new(), - }), - } - } -} - impl DefaultHeaders { /// Constructs an empty `DefaultHeaders` middleware. + #[inline] pub fn new() -> DefaultHeaders { DefaultHeaders::default() } /// Adds a header to the default set. - #[inline] - pub fn header(mut self, key: K, value: V) -> Self + /// + /// # Panics + /// Panics when resolved header name or value is invalid. + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, header: impl TryIntoHeaderPair) -> Self { + // standard header terminology `insert` or `append` for this method would make the behavior + // of this middleware less obvious since it only adds the headers if they are not present + + match header.try_into_pair() { + Ok((key, value)) => Rc::get_mut(&mut self.inner) + .expect("All default headers must be added before cloning.") + .headers + .append(key, value), + Err(err) => panic!("Invalid header: {}", err.into()), + } + + self + } + + #[doc(hidden)] + #[deprecated( + since = "4.0.0", + note = "Prefer `.add((key, value))`. Will be removed in v5." + )] + pub fn header(self, key: K, value: V) -> Self where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { - #[allow(clippy::match_wild_err_arm)] - match HeaderName::try_from(key) { - Ok(key) => match HeaderValue::try_from(value) { - Ok(value) => { - Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .headers - .append(key, value); - } - Err(_) => panic!("Can not create header value"), - }, - Err(_) => panic!("Can not create header name"), - } - self + self.add(( + HeaderName::try_from(key) + .map_err(Into::into) + .expect("Invalid header name"), + HeaderValue::try_from(value) + .map_err(Into::into) + .expect("Invalid header value"), + )) } /// Adds a default *Content-Type* header if response does not contain one. /// /// Default is `application/octet-stream`. - pub fn add_content_type(mut self) -> Self { - Rc::get_mut(&mut self.inner) - .expect("Multiple `Inner` copies exist.") - .headers - .insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - - self + pub fn add_content_type(self) -> Self { + self.add(( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + )) } } @@ -119,7 +121,7 @@ where fn new_transform(&self, service: S) -> Self::Future { ready(Ok(DefaultHeadersMiddleware { service, - inner: self.inner.clone(), + inner: Rc::clone(&self.inner), })) } } @@ -197,17 +199,22 @@ mod tests { }; #[actix_rt::test] - async fn test_default_headers() { + async fn adding_default_headers() { let mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") + .add(("X-TEST", "0001")) + .add(("X-TEST-TWO", HeaderValue::from_static("123"))) .new_transform(ok_service()) .await .unwrap(); let req = TestRequest::default().to_srv_request(); - let resp = mw.call(req).await.unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let res = mw.call(req).await.unwrap(); + assert_eq!(res.headers().get("x-test").unwrap(), "0001"); + assert_eq!(res.headers().get("x-test-two").unwrap(), "123"); + } + #[actix_rt::test] + async fn no_override_existing() { let req = TestRequest::default().to_srv_request(); let srv = |req: ServiceRequest| { ok(req.into_response( @@ -217,7 +224,7 @@ mod tests { )) }; let mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") + .add((CONTENT_TYPE, "0001")) .new_transform(srv.into_service()) .await .unwrap(); @@ -226,7 +233,7 @@ mod tests { } #[actix_rt::test] - async fn test_content_type() { + async fn adding_content_type() { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mw = DefaultHeaders::new() .add_content_type() @@ -241,4 +248,16 @@ mod tests { "application/octet-stream" ); } + + #[test] + #[should_panic] + fn invalid_header_name() { + DefaultHeaders::new().add((":", "hello")); + } + + #[test] + #[should_panic] + fn invalid_header_value() { + DefaultHeaders::new().add(("x-test", "\n")); + } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index d19cb64e9..42d285580 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -33,7 +33,7 @@ mod tests { let _ = App::new() .wrap(Compat::new(Logger::default())) .wrap(Condition::new(true, DefaultHeaders::new())) - .wrap(DefaultHeaders::new().header("X-Test2", "X-Value2")) + .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2"))) .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { Ok(ErrorHandlerResponse::Response(res)) })) @@ -46,7 +46,7 @@ mod tests { .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { Ok(ErrorHandlerResponse::Response(res)) })) - .wrap(DefaultHeaders::new().header("X-Test2", "X-Value2")) + .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2"))) .wrap(Condition::new(true, DefaultHeaders::new())) .wrap(Compat::new(Logger::default())); diff --git a/src/request_data.rs b/src/request_data.rs index 680f3e566..b685fd0d6 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -17,7 +17,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H /// # Mutating Request Data /// Note that since extractors must output owned data, only types that `impl Clone` can use this /// extractor. A clone is taken of the required request data and can, therefore, not be directly -/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or +/// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or /// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not /// provided to make this potential foot-gun more obvious. /// diff --git a/src/resource.rs b/src/resource.rs index 420374a86..53104930a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -15,13 +15,12 @@ use crate::{ dev::{ensure_leading_slash, AppService, ResourceDef}, guard::Guard, handler::Handler, - responder::Responder, route::{Route, RouteService}, service::{ BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, ServiceResponse, }, - BoxError, Error, FromRequest, HttpResponse, + BoxError, Error, FromRequest, HttpResponse, Responder, }; /// *Resource* is an entry in resources table which corresponds to requested URL. @@ -526,7 +525,7 @@ mod tests { .name("test") .wrap( DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), ) .route(web::get().to(HttpResponse::Ok)), ), diff --git a/src/response/builder.rs b/src/response/builder.rs index 18a1c8a7f..b500ab331 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -9,7 +9,7 @@ use std::{ use actix_http::{ body::{BodyStream, BoxBody, MessageBody}, error::HttpError, - header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, + header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue}, ConnectionType, Extensions, Response, ResponseHead, StatusCode, }; use bytes::Bytes; @@ -67,12 +67,9 @@ impl HttpResponseBuilder { /// .insert_header(("X-TEST", "value")) /// .finish(); /// ``` - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { + pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { if let Some(parts) = self.inner() { - match header.try_into_header_pair() { + match header.try_into_pair() { Ok((key, value)) => { parts.headers.insert(key, value); } @@ -94,12 +91,9 @@ impl HttpResponseBuilder { /// .append_header(("X-TEST", "value2")) /// .finish(); /// ``` - pub fn append_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { + pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { if let Some(parts) = self.inner() { - match header.try_into_header_pair() { + match header.try_into_pair() { Ok((key, value)) => parts.headers.append(key, value), Err(e) => self.err = Some(e.into()), }; @@ -118,7 +112,7 @@ impl HttpResponseBuilder { where K: TryInto, K::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if self.err.is_some() { return self; @@ -143,7 +137,7 @@ impl HttpResponseBuilder { where K: TryInto, K::Error: Into, - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if self.err.is_some() { return self; @@ -180,7 +174,7 @@ impl HttpResponseBuilder { #[inline] pub fn upgrade(&mut self, value: V) -> &mut Self where - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if let Some(parts) = self.inner() { parts.set_connection_type(ConnectionType::Upgrade); @@ -218,7 +212,7 @@ impl HttpResponseBuilder { #[inline] pub fn content_type(&mut self, value: V) -> &mut Self where - V: IntoHeaderValue, + V: TryIntoHeaderValue, { if let Some(parts) = self.inner() { match value.try_into_value() { diff --git a/src/response/customize_responder.rs b/src/response/customize_responder.rs new file mode 100644 index 000000000..11f6b2916 --- /dev/null +++ b/src/response/customize_responder.rs @@ -0,0 +1,245 @@ +use actix_http::{ + body::{EitherBody, MessageBody}, + error::HttpError, + header::HeaderMap, + header::TryIntoHeaderPair, + StatusCode, +}; + +use crate::{BoxError, HttpRequest, HttpResponse, Responder}; + +/// Allows overriding status code and headers for a [`Responder`]. +/// +/// Created by the [`Responder::customize`] method. +pub struct CustomizeResponder { + inner: CustomizeResponderInner, + error: Option, +} + +struct CustomizeResponderInner { + responder: R, + status: Option, + override_headers: HeaderMap, + append_headers: HeaderMap, +} + +impl CustomizeResponder { + pub(crate) fn new(responder: R) -> Self { + CustomizeResponder { + inner: CustomizeResponderInner { + responder, + status: None, + override_headers: HeaderMap::new(), + append_headers: HeaderMap::new(), + }, + error: None, + } + } + + /// Override a status code for the Responder's response. + /// + /// # Examples + /// ``` + /// use actix_web::{Responder, http::StatusCode, test::TestRequest}; + /// + /// let responder = "Welcome!".customize().with_status(StatusCode::ACCEPTED); + /// + /// let request = TestRequest::default().to_http_request(); + /// let response = responder.respond_to(&request); + /// assert_eq!(response.status(), StatusCode::ACCEPTED); + /// ``` + pub fn with_status(mut self, status: StatusCode) -> Self { + if let Some(inner) = self.inner() { + inner.status = Some(status); + } + + self + } + + /// Insert (override) header in the final response. + /// + /// Overrides other headers with the same name. + /// See [`HeaderMap::insert`](crate::http::header::HeaderMap::insert). + /// + /// Headers added with this method will be inserted before those added + /// with [`append_header`](Self::append_header). As such, header(s) can be overridden with more + /// than one new header by first calling `insert_header` followed by `append_header`. + /// + /// # Examples + /// ``` + /// use actix_web::{Responder, test::TestRequest}; + /// + /// let responder = "Hello world!" + /// .customize() + /// .insert_header(("x-version", "1.2.3")); + /// + /// let request = TestRequest::default().to_http_request(); + /// let response = responder.respond_to(&request); + /// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3"); + /// ``` + pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { + if let Some(inner) = self.inner() { + match header.try_into_pair() { + Ok((key, value)) => { + inner.override_headers.insert(key, value); + } + Err(err) => self.error = Some(err.into()), + }; + } + + self + } + + /// Append header to the final response. + /// + /// Unlike [`insert_header`](Self::insert_header), this will not override existing headers. + /// See [`HeaderMap::append`](crate::http::header::HeaderMap::append). + /// + /// Headers added here are appended _after_ additions/overrides from `insert_header`. + /// + /// # Examples + /// ``` + /// use actix_web::{Responder, test::TestRequest}; + /// + /// let responder = "Hello world!" + /// .customize() + /// .append_header(("x-version", "1.2.3")); + /// + /// let request = TestRequest::default().to_http_request(); + /// let response = responder.respond_to(&request); + /// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3"); + /// ``` + pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { + if let Some(inner) = self.inner() { + match header.try_into_pair() { + Ok((key, value)) => { + inner.append_headers.append(key, value); + } + Err(err) => self.error = Some(err.into()), + }; + } + + self + } + + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `insert_header`.")] + pub fn with_header(self, header: impl TryIntoHeaderPair) -> Self + where + Self: Sized, + { + self.insert_header(header) + } + + fn inner(&mut self) -> Option<&mut CustomizeResponderInner> { + if self.error.is_some() { + None + } else { + Some(&mut self.inner) + } + } +} + +impl Responder for CustomizeResponder +where + T: Responder, + ::Error: Into, +{ + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + if let Some(err) = self.error { + return HttpResponse::from_error(err).map_into_right_body(); + } + + let mut res = self.inner.responder.respond_to(req); + + if let Some(status) = self.inner.status { + *res.status_mut() = status; + } + + for (k, v) in self.inner.override_headers { + res.headers_mut().insert(k, v); + } + + for (k, v) in self.inner.append_headers { + res.headers_mut().append(k, v); + } + + res.map_into_left_body() + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + + use actix_http::body::to_bytes; + + use super::*; + use crate::{ + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, + test::TestRequest, + }; + + #[actix_rt::test] + async fn customize_responder() { + let req = TestRequest::default().to_http_request(); + let res = "test" + .to_string() + .customize() + .with_status(StatusCode::BAD_REQUEST) + .respond_to(&req); + + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let res = "test" + .to_string() + .customize() + .insert_header(("content-type", "json")) + .respond_to(&req); + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + } + + #[actix_rt::test] + async fn tuple_responder_with_status_code() { + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::OK) + .customize() + .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)) + .respond_to(&req); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/json") + ); + assert_eq!( + to_bytes(res.into_body()).await.unwrap(), + Bytes::from_static(b"test"), + ); + } +} diff --git a/src/response/mod.rs b/src/response/mod.rs index 8401db9d2..977147104 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -1,9 +1,13 @@ mod builder; +mod customize_responder; mod http_codes; +mod responder; #[allow(clippy::module_inception)] mod response; pub use self::builder::HttpResponseBuilder; +pub use self::customize_responder::CustomizeResponder; +pub use self::responder::Responder; pub use self::response::HttpResponse; #[cfg(feature = "cookies")] diff --git a/src/responder.rs b/src/response/responder.rs similarity index 63% rename from src/responder.rs rename to src/response/responder.rs index e72739a71..319b824f1 100644 --- a/src/responder.rs +++ b/src/response/responder.rs @@ -2,64 +2,58 @@ use std::borrow::Cow; use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, - error::HttpError, - header::HeaderMap, - header::IntoHeaderPair, + header::TryIntoHeaderPair, StatusCode, }; use bytes::{Bytes, BytesMut}; use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use super::CustomizeResponder; + /// Trait implemented by types that can be converted to an HTTP response. /// /// Any types that implement this trait can be used in the return type of a handler. +// # TODO: more about implementation notes and foreign impls pub trait Responder { type Body: MessageBody + 'static; /// Convert self to `HttpResponse`. fn respond_to(self, req: &HttpRequest) -> HttpResponse; - /// Override a status code for a Responder. + /// Wraps responder to allow alteration of its response. /// - /// ``` - /// use actix_web::{http::StatusCode, HttpRequest, Responder}; + /// See [`CustomizeResponder`] docs for its capabilities. /// - /// fn index(req: HttpRequest) -> impl Responder { - /// "Welcome!".with_status(StatusCode::OK) - /// } + /// # Examples /// ``` - fn with_status(self, status: StatusCode) -> CustomResponder + /// use actix_web::{Responder, http::StatusCode, test::TestRequest}; + /// + /// let responder = "Hello world!" + /// .customize() + /// .with_status(StatusCode::BAD_REQUEST) + /// .insert_header(("x-hello", "world")); + /// + /// let request = TestRequest::default().to_http_request(); + /// let response = responder.respond_to(&request); + /// assert_eq!(response.status(), StatusCode::BAD_REQUEST); + /// assert_eq!(response.headers().get("x-hello").unwrap(), "world"); + /// ``` + #[inline] + fn customize(self) -> CustomizeResponder where Self: Sized, { - CustomResponder::new(self).with_status(status) + CustomizeResponder::new(self) } - /// Insert header to the final response. - /// - /// Overrides other headers with the same name. - /// - /// ``` - /// use actix_web::{web, HttpRequest, Responder}; - /// use serde::Serialize; - /// - /// #[derive(Serialize)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json(MyObj { name: "Name".to_owned() }) - /// .with_header(("x-version", "1.2.3")) - /// } - /// ``` - fn with_header(self, header: H) -> CustomResponder + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header(header)`.")] + fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder where Self: Sized, - H: IntoHeaderPair, { - CustomResponder::new(self).with_header(header) + self.customize().insert_header(header) } } @@ -181,98 +175,6 @@ macro_rules! impl_into_string_responder { impl_into_string_responder!(&'_ String); impl_into_string_responder!(Cow<'_, str>); -/// Allows overriding status code and headers for a responder. -pub struct CustomResponder { - responder: T, - status: Option, - headers: Result, -} - -impl CustomResponder { - fn new(responder: T) -> Self { - CustomResponder { - responder, - status: None, - headers: Ok(HeaderMap::new()), - } - } - - /// Override a status code for the Responder's response. - /// - /// ``` - /// use actix_web::{HttpRequest, Responder, http::StatusCode}; - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// "Welcome!".with_status(StatusCode::OK) - /// } - /// ``` - pub fn with_status(mut self, status: StatusCode) -> Self { - self.status = Some(status); - self - } - - /// Insert header to the final response. - /// - /// Overrides other headers with the same name. - /// - /// ``` - /// use actix_web::{web, HttpRequest, Responder}; - /// use serde::Serialize; - /// - /// #[derive(Serialize)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json(MyObj { name: "Name".to_string() }) - /// .with_header(("x-version", "1.2.3")) - /// .with_header(("x-version", "1.2.3")) - /// } - /// ``` - pub fn with_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { - if let Ok(ref mut headers) = self.headers { - match header.try_into_header_pair() { - Ok((key, value)) => headers.append(key, value), - Err(e) => self.headers = Err(e.into()), - }; - } - - self - } -} - -impl Responder for CustomResponder -where - T: Responder, - ::Error: Into, -{ - type Body = EitherBody; - - fn respond_to(self, req: &HttpRequest) -> HttpResponse { - let headers = match self.headers { - Ok(headers) => headers, - Err(err) => return HttpResponse::from_error(err).map_into_right_body(), - }; - - let mut res = self.responder.respond_to(req); - - if let Some(status) = self.status { - *res.status_mut() = status; - } - - for (k, v) in headers { - // TODO: before v4, decide if this should be append instead - res.headers_mut().insert(k, v); - } - - res.map_into_left_body() - } -} - #[cfg(test)] pub(crate) mod tests { use actix_service::Service; @@ -440,59 +342,4 @@ pub(crate) mod tests { assert_eq!(res.status(), StatusCode::BAD_REQUEST); } - - #[actix_rt::test] - async fn test_custom_responder() { - let req = TestRequest::default().to_http_request(); - let res = "test" - .to_string() - .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req); - - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!( - to_bytes(res.into_body()).await.unwrap(), - Bytes::from_static(b"test"), - ); - - let res = "test" - .to_string() - .with_header(("content-type", "json")) - .respond_to(&req); - - assert_eq!(res.status(), StatusCode::OK); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); - assert_eq!( - to_bytes(res.into_body()).await.unwrap(), - Bytes::from_static(b"test"), - ); - } - - #[actix_rt::test] - async fn test_tuple_responder_with_status_code() { - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!( - to_bytes(res.into_body()).await.unwrap(), - Bytes::from_static(b"test"), - ); - - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::OK) - .with_header((CONTENT_TYPE, mime::APPLICATION_JSON)) - .respond_to(&req); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/json") - ); - assert_eq!( - to_bytes(res.into_body()).await.unwrap(), - Bytes::from_static(b"test"), - ); - } } diff --git a/src/scope.rs b/src/scope.rs index 74523cd94..c35584770 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -935,7 +935,7 @@ mod tests { web::scope("app") .wrap( DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), ) .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), diff --git a/src/test.rs b/src/test.rs index cfb3ef8f2..5ef2343a8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,8 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc}; pub use actix_http::test::TestBuffer; use actix_http::{ - header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request, - StatusCode, Uri, Version, + header::TryIntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, + Request, StatusCode, Uri, Version, }; use actix_router::{Path, ResourceDef, Url}; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; @@ -445,19 +445,13 @@ impl TestRequest { } /// Insert a header, replacing any that were set with an equivalent field name. - pub fn insert_header(mut self, header: H) -> Self - where - H: IntoHeaderPair, - { + pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { self.req.insert_header(header); self } /// 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, - { + pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { self.req.append_header(header); self } diff --git a/src/web.rs b/src/web.rs index 16dbace60..042b8a008 100644 --- a/src/web.rs +++ b/src/web.rs @@ -8,7 +8,7 @@ pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::{ body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler, - resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService, + resource::Resource, route::Route, scope::Scope, service::WebService, Responder, }; pub use crate::config::ServiceConfig; From fb091b2b88f9590d449415f272bd763d3ad4df3c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 14 Dec 2021 18:33:17 +0000 Subject: [PATCH 175/861] split up router pattern and resource_path modules --- actix-router/src/lib.rs | 142 ++---------------------------- actix-router/src/pattern.rs | 92 +++++++++++++++++++ actix-router/src/resource_path.rs | 36 ++++++++ 3 files changed, 137 insertions(+), 133 deletions(-) create mode 100644 actix-router/src/pattern.rs create mode 100644 actix-router/src/resource_path.rs diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index f616f7fc6..03f464626 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -7,144 +7,20 @@ mod de; mod path; +mod pattern; mod resource; +mod resource_path; mod router; -pub use self::de::PathDeserializer; -pub use self::path::Path; -pub use self::resource::ResourceDef; -pub use self::router::{ResourceInfo, Router, RouterBuilder}; - -// TODO: this trait is necessary, document it -// see impl Resource for ServiceRequest -pub trait Resource { - fn resource_path(&mut self) -> &mut Path; -} - -pub trait ResourcePath { - fn path(&self) -> &str; -} - -impl ResourcePath for String { - fn path(&self) -> &str { - self.as_str() - } -} - -impl<'a> ResourcePath for &'a str { - fn path(&self) -> &str { - self - } -} - -impl ResourcePath for bytestring::ByteString { - fn path(&self) -> &str { - &*self - } -} - -/// One or many patterns. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Patterns { - Single(String), - List(Vec), -} - -impl Patterns { - pub fn is_empty(&self) -> bool { - match self { - Patterns::Single(_) => false, - Patterns::List(pats) => pats.is_empty(), - } - } -} - -/// Helper trait for type that could be converted to one or more path pattern. -pub trait IntoPatterns { - fn patterns(&self) -> Patterns; -} - -impl IntoPatterns for String { - fn patterns(&self) -> Patterns { - Patterns::Single(self.clone()) - } -} - -impl<'a> IntoPatterns for &'a String { - fn patterns(&self) -> Patterns { - Patterns::Single((*self).clone()) - } -} - -impl<'a> IntoPatterns for &'a str { - fn patterns(&self) -> Patterns { - Patterns::Single((*self).to_owned()) - } -} - -impl IntoPatterns for bytestring::ByteString { - fn patterns(&self) -> Patterns { - Patterns::Single(self.to_string()) - } -} - -impl IntoPatterns for Patterns { - fn patterns(&self) -> Patterns { - self.clone() - } -} - -impl> IntoPatterns for Vec { - fn patterns(&self) -> Patterns { - let mut patterns = self.iter().map(|v| v.as_ref().to_owned()); - - match patterns.size_hint() { - (1, _) => Patterns::Single(patterns.next().unwrap()), - _ => Patterns::List(patterns.collect()), - } - } -} - -macro_rules! array_patterns_single (($tp:ty) => { - impl IntoPatterns for [$tp; 1] { - fn patterns(&self) -> Patterns { - Patterns::Single(self[0].to_owned()) - } - } -}); - -macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => { - // for each array length specified in $num - $( - impl IntoPatterns for [$tp; $num] { - fn patterns(&self) -> Patterns { - Patterns::List(self.iter().map($str_fn).collect()) - } - } - )+ -}); - -array_patterns_single!(&str); -array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); - -array_patterns_single!(String); -array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); - #[cfg(feature = "http")] mod url; +pub use self::de::PathDeserializer; +pub use self::path::Path; +pub use self::pattern::{IntoPatterns, Patterns}; +pub use self::resource::ResourceDef; +pub use self::resource_path::{Resource, ResourcePath}; +pub use self::router::{ResourceInfo, Router, RouterBuilder}; + #[cfg(feature = "http")] pub use self::url::{Quoter, Url}; - -#[cfg(feature = "http")] -mod http_impls { - use http::Uri; - - use super::ResourcePath; - - impl ResourcePath for Uri { - fn path(&self) -> &str { - self.path() - } - } -} diff --git a/actix-router/src/pattern.rs b/actix-router/src/pattern.rs new file mode 100644 index 000000000..78a638a78 --- /dev/null +++ b/actix-router/src/pattern.rs @@ -0,0 +1,92 @@ +/// One or many patterns. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Patterns { + Single(String), + List(Vec), +} + +impl Patterns { + pub fn is_empty(&self) -> bool { + match self { + Patterns::Single(_) => false, + Patterns::List(pats) => pats.is_empty(), + } + } +} + +/// Helper trait for type that could be converted to one or more path patterns. +pub trait IntoPatterns { + fn patterns(&self) -> Patterns; +} + +impl IntoPatterns for String { + fn patterns(&self) -> Patterns { + Patterns::Single(self.clone()) + } +} + +impl IntoPatterns for &String { + fn patterns(&self) -> Patterns { + (*self).patterns() + } +} + +impl IntoPatterns for str { + fn patterns(&self) -> Patterns { + Patterns::Single(self.to_owned()) + } +} + +impl IntoPatterns for &str { + fn patterns(&self) -> Patterns { + (*self).patterns() + } +} + +impl IntoPatterns for bytestring::ByteString { + fn patterns(&self) -> Patterns { + Patterns::Single(self.to_string()) + } +} + +impl IntoPatterns for Patterns { + fn patterns(&self) -> Patterns { + self.clone() + } +} + +impl> IntoPatterns for Vec { + fn patterns(&self) -> Patterns { + let mut patterns = self.iter().map(|v| v.as_ref().to_owned()); + + match patterns.size_hint() { + (1, _) => Patterns::Single(patterns.next().unwrap()), + _ => Patterns::List(patterns.collect()), + } + } +} + +macro_rules! array_patterns_single (($tp:ty) => { + impl IntoPatterns for [$tp; 1] { + fn patterns(&self) -> Patterns { + Patterns::Single(self[0].to_owned()) + } + } +}); + +macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => { + // for each array length specified in space-separated $num + $( + impl IntoPatterns for [$tp; $num] { + fn patterns(&self) -> Patterns { + Patterns::List(self.iter().map($str_fn).collect()) + } + } + )+ +}); + +array_patterns_single!(&str); +array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); + +array_patterns_single!(String); +array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); diff --git a/actix-router/src/resource_path.rs b/actix-router/src/resource_path.rs new file mode 100644 index 000000000..91a7f2f55 --- /dev/null +++ b/actix-router/src/resource_path.rs @@ -0,0 +1,36 @@ +use crate::Path; + +// TODO: this trait is necessary, document it +// see impl Resource for ServiceRequest +pub trait Resource { + fn resource_path(&mut self) -> &mut Path; +} + +pub trait ResourcePath { + fn path(&self) -> &str; +} + +impl ResourcePath for String { + fn path(&self) -> &str { + self.as_str() + } +} + +impl<'a> ResourcePath for &'a str { + fn path(&self) -> &str { + self + } +} + +impl ResourcePath for bytestring::ByteString { + fn path(&self) -> &str { + &*self + } +} + +#[cfg(feature = "http")] +impl ResourcePath for http::Uri { + fn path(&self) -> &str { + self.path() + } +} From 05255c7f7c92d785bac919f39374efa9985eec57 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 14 Dec 2021 19:57:18 +0000 Subject: [PATCH 176/861] remove either crate conversions (#2516) --- CHANGES.md | 2 ++ Cargo.toml | 1 - src/types/either.rs | 25 ++++--------------------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2df820027..b8d3ce8de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,8 +10,10 @@ ### Removed * Top-level `EitherExtractError` export. [#2510] +* Conversion implementations for `either` crate. [#2516] [#2510]: https://github.com/actix/actix-web/pull/2510 +[#2516]: https://github.com/actix/actix-web/pull/2516 ## 4.0.0-beta.14 - 2021-12-11 diff --git a/Cargo.toml b/Cargo.toml index 96e2dd797..e20529e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,6 @@ bytes = "1" cfg-if = "1" cookie = { version = "0.15", features = ["percent-encode"], optional = true } derive_more = "0.99.5" -either = "1.5.3" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } diff --git a/src/types/either.rs b/src/types/either.rs index 3c759736e..5b8e02525 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -12,7 +12,8 @@ use futures_core::ready; use pin_project_lite::pin_project; use crate::{ - body, dev, + body::EitherBody, + dev, web::{Form, Json}, Error, FromRequest, HttpRequest, HttpResponse, Responder, }; @@ -101,24 +102,6 @@ impl Either, Form> { } } -impl From> for Either { - fn from(val: either::Either) -> Self { - match val { - either::Either::Left(l) => Either::Left(l), - either::Either::Right(r) => Either::Right(r), - } - } -} - -impl From> for either::Either { - fn from(val: Either) -> Self { - match val { - Either::Left(l) => either::Either::Left(l), - Either::Right(r) => either::Either::Right(r), - } - } -} - #[cfg(test)] impl Either { pub(self) fn unwrap_left(self) -> L { @@ -146,7 +129,7 @@ where L: Responder, R: Responder, { - type Body = body::EitherBody; + type Body = EitherBody; fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { @@ -165,7 +148,7 @@ pub enum EitherExtractError { /// Error from payload buffering, such as exceeding payload max size limit. Bytes(Error), - /// Error from primary extractor. + /// Error from primary and fallback extractors. Extract(L, R), } From dd4a372613339f6668dc248d2dbc414c3a6ccfad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 14 Dec 2021 21:17:50 +0000 Subject: [PATCH 177/861] allow error handler middleware to return different body type (#2515) --- CHANGES.md | 3 + actix-router/src/url.rs | 53 ++++++++------ scripts/ci-test | 32 ++++++--- src/middleware/compat.rs | 22 +++--- src/middleware/condition.rs | 13 ++-- src/middleware/err_handlers.rs | 128 +++++++++++++++++++++------------ src/middleware/mod.rs | 4 +- 7 files changed, 162 insertions(+), 93 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b8d3ce8de..0c27aaa1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,12 +7,15 @@ ### Changed * Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +* Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] ### Removed * Top-level `EitherExtractError` export. [#2510] * Conversion implementations for `either` crate. [#2516] [#2510]: https://github.com/actix/actix-web/pull/2510 +[#2515]: https://github.com/actix/actix-web/pull/2515 [#2516]: https://github.com/actix/actix-web/pull/2516 diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index e08a7171a..10193dde8 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -2,22 +2,28 @@ use crate::ResourcePath; #[allow(dead_code)] const GEN_DELIMS: &[u8] = b":/?#[]@"; + #[allow(dead_code)] const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; + #[allow(dead_code)] const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; + #[allow(dead_code)] const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; + #[allow(dead_code)] const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -._~"; + const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -._~ !$'()*,"; + const QS: &[u8] = b"+&=;b"; #[inline] @@ -34,19 +40,20 @@ thread_local! { static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); } -#[derive(Default, Clone, Debug)] +#[derive(Debug, Clone, Default)] pub struct Url { uri: http::Uri, path: Option, } impl Url { + #[inline] pub fn new(uri: http::Uri) -> Url { let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); - Url { uri, path } } + #[inline] pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { Url { path: quoter.requote(uri.path().as_bytes()), @@ -54,15 +61,16 @@ impl Url { } } + #[inline] pub fn uri(&self) -> &http::Uri { &self.uri } + #[inline] pub fn path(&self) -> &str { - if let Some(ref s) = self.path { - s - } else { - self.uri.path() + match self.path { + Some(ref path) => path, + _ => self.uri.path(), } } @@ -86,6 +94,7 @@ impl ResourcePath for Url { } } +/// A quoter pub struct Quoter { safe_table: [u8; 16], protected_table: [u8; 16], @@ -93,7 +102,7 @@ pub struct Quoter { impl Quoter { pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut q = Quoter { + let mut quoter = Quoter { safe_table: [0; 16], protected_table: [0; 16], }; @@ -101,24 +110,24 @@ impl Quoter { // prepare safe table for i in 0..128 { if ALLOWED.contains(&i) { - set_bit(&mut q.safe_table, i); + set_bit(&mut quoter.safe_table, i); } if QS.contains(&i) { - set_bit(&mut q.safe_table, i); + set_bit(&mut quoter.safe_table, i); } } for ch in safe { - set_bit(&mut q.safe_table, *ch) + set_bit(&mut quoter.safe_table, *ch) } // prepare protected table for ch in protected { - set_bit(&mut q.safe_table, *ch); - set_bit(&mut q.protected_table, *ch); + set_bit(&mut quoter.safe_table, *ch); + set_bit(&mut quoter.protected_table, *ch); } - q + quoter } pub fn requote(&self, val: &[u8]) -> Option { @@ -215,7 +224,7 @@ mod tests { } #[test] - fn test_parse_url() { + fn parse_url() { let re = "/user/{id}/test"; let path = match_url(re, "/user/2345/test"); @@ -231,24 +240,24 @@ mod tests { } #[test] - fn test_protected_chars() { + fn protected_chars() { let encoded = percent_encode(PROTECTED); let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); assert_eq!(path.get("id").unwrap(), &encoded); } #[test] - fn test_non_protecteed_ascii() { - let nonprotected_ascii = ('\u{0}'..='\u{7F}') + fn non_protected_ascii() { + let non_protected_ascii = ('\u{0}'..='\u{7F}') .filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8))) .collect::(); - let encoded = percent_encode(nonprotected_ascii.as_bytes()); + let encoded = percent_encode(non_protected_ascii.as_bytes()); let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); - assert_eq!(path.get("id").unwrap(), &nonprotected_ascii); + assert_eq!(path.get("id").unwrap(), &non_protected_ascii); } #[test] - fn test_valid_utf8_multibyte() { + fn valid_utf8_multibyte() { let test = ('\u{FF00}'..='\u{FFFF}').collect::(); let encoded = percent_encode(test.as_bytes()); let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded)); @@ -256,7 +265,7 @@ mod tests { } #[test] - fn test_invalid_utf8() { + fn invalid_utf8() { let invalid_utf8 = percent_encode((0x80..=0xff).collect::>().as_slice()); let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap(); let path = Path::new(Url::new(uri)); @@ -266,7 +275,7 @@ mod tests { } #[test] - fn test_from_hex() { + fn hex_encoding() { let hex = b"0123456789abcdefABCDEF"; for i in 0..256 { diff --git a/scripts/ci-test b/scripts/ci-test index 98e13927d..3ab229665 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -4,15 +4,25 @@ set -x -cargo test --lib --tests -p=actix-router --all-features -cargo test --lib --tests -p=actix-http --all-features -cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls -cargo test --lib --tests -p=actix-web-codegen --all-features -cargo test --lib --tests -p=awc --all-features -cargo test --lib --tests -p=actix-http-test --all-features -cargo test --lib --tests -p=actix-test --all-features -cargo test --lib --tests -p=actix-files -cargo test --lib --tests -p=actix-multipart --all-features -cargo test --lib --tests -p=actix-web-actors --all-features +EXIT=0 -cargo test --workspace --doc +save_exit_code() { + eval $@ + local CMD_EXIT=$? + [ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT +} + +save_exit_code cargo test --lib --tests -p=actix-router --all-features +save_exit_code cargo test --lib --tests -p=actix-http --all-features +save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls +save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features +save_exit_code cargo test --lib --tests -p=awc --all-features +save_exit_code cargo test --lib --tests -p=actix-http-test --all-features +save_exit_code cargo test --lib --tests -p=actix-test --all-features +save_exit_code cargo test --lib --tests -p=actix-files +save_exit_code cargo test --lib --tests -p=actix-multipart --all-features +save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features + +save_exit_code cargo test --workspace --doc + +exit $EXIT diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index ed441f7b9..d49c461c4 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -6,12 +6,15 @@ use std::{ task::{Context, Poll}, }; -use actix_http::body::MessageBody; -use actix_service::{Service, Transform}; use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; -use crate::{error::Error, service::ServiceResponse}; +use crate::{ + body::{BoxBody, MessageBody}, + dev::{Service, Transform}, + error::Error, + service::ServiceResponse, +}; /// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), /// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). @@ -52,7 +55,7 @@ where T::Response: MapServiceResponseBody, T::Error: Into, { - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type Transform = CompatMiddleware; type InitError = T::InitError; @@ -77,7 +80,7 @@ where S::Response: MapServiceResponseBody, S::Error: Into, { - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type Future = CompatMiddlewareFuture; @@ -102,7 +105,7 @@ where T: MapServiceResponseBody, E: Into, { - type Output = Result; + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let res = match ready!(self.project().fut.poll(cx)) { @@ -116,14 +119,15 @@ where /// Convert `ServiceResponse`'s `ResponseBody` generic type to `ResponseBody`. pub trait MapServiceResponseBody { - fn map_body(self) -> ServiceResponse; + fn map_body(self) -> ServiceResponse; } impl MapServiceResponseBody for ServiceResponse where - B: MessageBody + Unpin + 'static, + B: MessageBody + 'static, { - fn map_body(self) -> ServiceResponse { + #[inline] + fn map_body(self) -> ServiceResponse { self.map_into_boxed_body() } } diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index a7777a96b..659f88bc9 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -106,7 +106,7 @@ mod tests { header::{HeaderValue, CONTENT_TYPE}, StatusCode, }, - middleware::err_handlers::*, + middleware::{err_handlers::*, Compat}, test::{self, TestRequest}, HttpResponse, }; @@ -116,7 +116,8 @@ mod tests { res.response_mut() .headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Response(res)) + + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } #[actix_rt::test] @@ -125,7 +126,9 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = Compat::new( + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), + ); let mw = Condition::new(true, mw) .new_transform(srv.into_service()) @@ -141,7 +144,9 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = Compat::new( + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), + ); let mw = Condition::new(false, mw) .new_transform(srv.into_service()) diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 756da30c3..fedefa6fa 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -13,6 +13,7 @@ use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; use crate::{ + body::EitherBody, dev::{ServiceRequest, ServiceResponse}, http::StatusCode, Error, Result, @@ -21,10 +22,10 @@ use crate::{ /// Return type for [`ErrorHandlers`] custom handlers. pub enum ErrorHandlerResponse { /// Immediate HTTP response. - Response(ServiceResponse), + Response(ServiceResponse>), /// A future that resolves to an HTTP response. - Future(LocalBoxFuture<'static, Result, Error>>), + Future(LocalBoxFuture<'static, Result>, Error>>), } type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; @@ -44,7 +45,8 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result = Rc>>>; impl Default for ErrorHandlers { fn default() -> Self { ErrorHandlers { - handlers: Rc::new(AHashMap::default()), + handlers: Default::default(), } } } @@ -95,7 +97,7 @@ where S::Future: 'static, B: 'static, { - type Response = ServiceResponse; + type Response = ServiceResponse>; type Error = Error; type Transform = ErrorHandlersMiddleware; type InitError = (); @@ -119,7 +121,7 @@ where S::Future: 'static, B: 'static, { - type Response = ServiceResponse; + type Response = ServiceResponse>; type Error = Error; type Future = ErrorHandlersFuture; @@ -143,8 +145,8 @@ pin_project! { fut: Fut, handlers: Handlers, }, - HandlerFuture { - fut: LocalBoxFuture<'static, Fut::Output>, + ErrorHandlerFuture { + fut: LocalBoxFuture<'static, Result>, Error>>, }, } } @@ -153,25 +155,29 @@ impl Future for ErrorHandlersFuture where Fut: Future, Error>>, { - type Output = Fut::Output; + type Output = Result>, Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project() { ErrorHandlersProj::ServiceFuture { fut, handlers } => { let res = ready!(fut.poll(cx))?; + match handlers.get(&res.status()) { Some(handler) => match handler(res)? { ErrorHandlerResponse::Response(res) => Poll::Ready(Ok(res)), ErrorHandlerResponse::Future(fut) => { self.as_mut() - .set(ErrorHandlersFuture::HandlerFuture { fut }); + .set(ErrorHandlersFuture::ErrorHandlerFuture { fut }); + self.poll(cx) } }, - None => Poll::Ready(Ok(res)), + + None => Poll::Ready(Ok(res.map_into_left_body())), } } - ErrorHandlersProj::HandlerFuture { fut } => fut.as_mut().poll(cx), + + ErrorHandlersProj::ErrorHandlerFuture { fut } => fut.as_mut().poll(cx), } } } @@ -180,32 +186,33 @@ where mod tests { use actix_service::IntoService; use actix_utils::future::ok; + use bytes::Bytes; use futures_util::future::FutureExt as _; use super::*; - use crate::http::{ - header::{HeaderValue, CONTENT_TYPE}, - StatusCode, + use crate::{ + http::{ + header::{HeaderValue, CONTENT_TYPE}, + StatusCode, + }, + test::{self, TestRequest}, }; - use crate::test::{self, TestRequest}; - use crate::HttpResponse; - - #[allow(clippy::unnecessary_wraps)] - fn render_500(mut res: ServiceResponse) -> Result> { - res.response_mut() - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Response(res)) - } #[actix_rt::test] - async fn test_handler() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + async fn add_header_error_handler() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler(mut res: ServiceResponse) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) + .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) .new_transform(srv.into_service()) .await .unwrap(); @@ -214,24 +221,25 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } - #[allow(clippy::unnecessary_wraps)] - fn render_500_async( - mut res: ServiceResponse, - ) -> Result> { - res.response_mut() - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Future(ok(res).boxed_local())) - } - #[actix_rt::test] - async fn test_handler_async() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + async fn add_header_error_handler_async() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + + Ok(ErrorHandlerResponse::Future( + ok(res.map_into_left_body()).boxed_local(), + )) + } + + let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) + .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) .new_transform(srv.into_service()) .await .unwrap(); @@ -239,4 +247,34 @@ mod tests { let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } + + #[actix_rt::test] + async fn changes_body_type() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler( + res: ServiceResponse, + ) -> Result> { + let (req, res) = res.into_parts(); + let res = res.set_body(Bytes::from("sorry, that's no bueno")); + + let res = ServiceResponse::new(req, res) + .map_into_boxed_body() + .map_into_right_body(); + + Ok(ErrorHandlerResponse::Response(res)) + } + + let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); + + let mw = ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) + .new_transform(srv.into_service()) + .await + .unwrap(); + + let res = test::call_service(&mw, TestRequest::default().to_srv_request()).await; + assert_eq!(test::read_body(res).await, "sorry, that's no bueno"); + } + + // TODO: test where error is thrown } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 42d285580..0da9b9b2e 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -35,7 +35,7 @@ mod tests { .wrap(Condition::new(true, DefaultHeaders::new())) .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2"))) .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { - Ok(ErrorHandlerResponse::Response(res)) + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) })) .wrap(Logger::default()) .wrap(NormalizePath::new(TrailingSlash::Trim)); @@ -44,7 +44,7 @@ mod tests { .wrap(NormalizePath::new(TrailingSlash::Trim)) .wrap(Logger::default()) .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { - Ok(ErrorHandlerResponse::Response(res)) + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) })) .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2"))) .wrap(Condition::new(true, DefaultHeaders::new())) From 156cc20ac8af6455cb2438ba1b982265bac64521 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 15 Dec 2021 01:44:51 +0000 Subject: [PATCH 178/861] refactor testing utils (#2518) --- CHANGES.md | 6 + actix-http/src/test.rs | 2 +- actix-test/CHANGES.md | 4 + actix-test/src/lib.rs | 13 +- src/middleware/default_headers.rs | 7 +- src/middleware/err_handlers.rs | 6 +- src/test.rs | 909 ------------------------------ src/test/mod.rs | 81 +++ src/test/test_request.rs | 431 ++++++++++++++ src/test/test_services.rs | 31 + src/test/test_utils.rs | 474 ++++++++++++++++ src/types/either.rs | 2 - src/types/json.rs | 5 +- 13 files changed, 1043 insertions(+), 928 deletions(-) delete mode 100644 src/test.rs create mode 100644 src/test/mod.rs create mode 100644 src/test/test_request.rs create mode 100644 src/test/test_services.rs create mode 100644 src/test/test_utils.rs diff --git a/CHANGES.md b/CHANGES.md index 0c27aaa1c..6494ba4f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,14 +9,20 @@ * Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] * Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] * Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] +* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] +* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +* Relax body type and error bounds on test utilities. ### Removed * Top-level `EitherExtractError` export. [#2510] * Conversion implementations for `either` crate. [#2516] +* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2515]: https://github.com/actix/actix-web/pull/2515 [#2516]: https://github.com/actix/actix-web/pull/2516 +[#2518]: https://github.com/actix/actix-web/pull/2518 ## 4.0.0-beta.14 - 2021-12-11 diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 7e26ee865..ea80345fe 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -264,7 +264,7 @@ impl TestSeqBuffer { /// Create new empty `TestBuffer` instance. pub fn empty() -> Self { - Self::new("") + Self::new(BytesMut::new()) } pub fn read_buf(&self) -> Ref<'_, BytesMut> { diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index ec7d3e8d1..b7107b44f 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +* Re-export `actix_http::body::to_bytes`. [#2518] +* Update `actix_web::test` re-exports. [#2518] + +[#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 934b8f3aa..3808ba69a 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -37,9 +37,14 @@ extern crate tls_rustls as rustls; use std::{fmt, net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -pub use actix_http::test::TestBuffer; +pub use actix_http::{body::to_bytes, test::TestBuffer}; use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response}; +pub use actix_http_test::unused_addr; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; +pub use actix_web::test::{ + call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service, + read_body, read_body_json, simple_service, TestRequest, +}; use actix_web::{ body::MessageBody, dev::{AppConfig, Server, ServerHandle, Service}, @@ -48,12 +53,6 @@ use actix_web::{ }; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use futures_core::Stream; - -pub use actix_http_test::unused_addr; -pub use actix_web::test::{ - call_service, default_service, init_service, load_stream, ok_service, read_body, - read_body_json, read_response, read_response_json, TestRequest, -}; use tokio::sync::mpsc; /// Start default [`TestServer`]. diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 257467710..89210b156 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -194,7 +194,7 @@ mod tests { use crate::{ dev::ServiceRequest, http::header::CONTENT_TYPE, - test::{ok_service, TestRequest}, + test::{self, TestRequest}, HttpResponse, }; @@ -203,7 +203,7 @@ mod tests { let mw = DefaultHeaders::new() .add(("X-TEST", "0001")) .add(("X-TEST-TWO", HeaderValue::from_static("123"))) - .new_transform(ok_service()) + .new_transform(test::ok_service()) .await .unwrap(); @@ -234,10 +234,9 @@ mod tests { #[actix_rt::test] async fn adding_content_type() { - let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mw = DefaultHeaders::new() .add_content_type() - .new_transform(srv.into_service()) + .new_transform(test::ok_service()) .await .unwrap(); diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index fedefa6fa..6d064372f 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -209,7 +209,7 @@ mod tests { Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } - let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) @@ -236,7 +236,7 @@ mod tests { )) } - let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) @@ -264,7 +264,7 @@ mod tests { Ok(ErrorHandlerResponse::Response(res)) } - let srv = test::default_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 5ef2343a8..000000000 --- a/src/test.rs +++ /dev/null @@ -1,909 +0,0 @@ -//! Various helpers for Actix applications to use during testing. - -use std::{borrow::Cow, net::SocketAddr, rc::Rc}; - -pub use actix_http::test::TestBuffer; -use actix_http::{ - header::TryIntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, - Request, StatusCode, Uri, Version, -}; -use actix_router::{Path, ResourceDef, Url}; -use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; -use actix_utils::future::{ok, poll_fn}; -use futures_core::Stream; -use futures_util::StreamExt as _; -use serde::{de::DeserializeOwned, Serialize}; - -#[cfg(feature = "cookies")] -use crate::cookie::{Cookie, CookieJar}; -use crate::{ - app_service::AppInitServiceState, - body::{self, BoxBody, MessageBody}, - config::AppConfig, - data::Data, - dev::Payload, - http::header::ContentType, - rmap::ResourceMap, - service::{ServiceRequest, ServiceResponse}, - web::{Bytes, BytesMut}, - Error, HttpRequest, HttpResponse, HttpResponseBuilder, -}; - -/// Create service that always responds with `HttpResponse::Ok()` and no body. -pub fn ok_service( -) -> impl Service, Error = Error> { - default_service(StatusCode::OK) -} - -/// Create service that always responds with given status code and no body. -pub fn default_service( - status_code: StatusCode, -) -> impl Service, Error = Error> { - (move |req: ServiceRequest| { - ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) - }) - .into_service() -} - -/// Initialize service from application builder instance. -/// -/// ``` -/// use actix_service::Service; -/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; -/// -/// #[actix_web::test] -/// async fn test_init_service() { -/// let app = test::init_service( -/// App::new() -/// .service(web::resource("/test").to(|| async { "OK" })) -/// ).await; -/// -/// // Create request object -/// let req = test::TestRequest::with_uri("/test").to_request(); -/// -/// // Execute application -/// let resp = app.call(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// } -/// ``` -pub async fn init_service( - app: R, -) -> impl Service, Error = E> -where - R: IntoServiceFactory, - S: ServiceFactory, Error = E>, - S::InitError: std::fmt::Debug, -{ - try_init_service(app) - .await - .expect("service initialization failed") -} - -/// Fallible version of [`init_service`] that allows testing initialization errors. -pub(crate) async fn try_init_service( - app: R, -) -> Result, Error = E>, S::InitError> -where - R: IntoServiceFactory, - S: ServiceFactory, Error = E>, - S::InitError: std::fmt::Debug, -{ - let srv = app.into_factory(); - srv.new_service(AppConfig::default()).await -} - -/// Calls service and waits for response future completion. -/// -/// ``` -/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; -/// -/// #[actix_web::test] -/// async fn test_response() { -/// let app = test::init_service( -/// App::new() -/// .service(web::resource("/test").to(|| async { -/// HttpResponse::Ok() -/// })) -/// ).await; -/// -/// // Create request object -/// let req = test::TestRequest::with_uri("/test").to_request(); -/// -/// // Call application -/// let resp = test::call_service(&app, req).await; -/// assert_eq!(resp.status(), StatusCode::OK); -/// } -/// ``` -pub async fn call_service(app: &S, req: R) -> S::Response -where - S: Service, Error = E>, - E: std::fmt::Debug, -{ - app.call(req).await.unwrap() -} - -/// Helper function that returns a response body of a TestRequest -/// -/// ``` -/// use actix_web::{test, web, App, HttpResponse, http::header}; -/// use bytes::Bytes; -/// -/// #[actix_web::test] -/// async fn test_index() { -/// let app = test::init_service( -/// App::new().service( -/// web::resource("/index.html") -/// .route(web::post().to(|| async { -/// HttpResponse::Ok().body("welcome!") -/// }))) -/// ).await; -/// -/// let req = test::TestRequest::post() -/// .uri("/index.html") -/// .header(header::CONTENT_TYPE, "application/json") -/// .to_request(); -/// -/// let result = test::read_response(&app, req).await; -/// assert_eq!(result, Bytes::from_static(b"welcome!")); -/// } -/// ``` -pub async fn read_response(app: &S, req: Request) -> Bytes -where - S: Service, Error = Error>, - B: MessageBody + Unpin, - B::Error: Into, -{ - let resp = app - .call(req) - .await - .unwrap_or_else(|e| panic!("read_response failed at application call: {}", e)); - - let body = resp.into_body(); - let mut bytes = BytesMut::new(); - - actix_rt::pin!(body); - while let Some(item) = poll_fn(|cx| body.as_mut().poll_next(cx)).await { - bytes.extend_from_slice(&item.map_err(Into::into).unwrap()); - } - - bytes.freeze() -} - -/// Helper function that returns a response body of a ServiceResponse. -/// -/// ``` -/// use actix_web::{test, web, App, HttpResponse, http::header}; -/// use bytes::Bytes; -/// -/// #[actix_web::test] -/// async fn test_index() { -/// let app = test::init_service( -/// App::new().service( -/// web::resource("/index.html") -/// .route(web::post().to(|| async { -/// HttpResponse::Ok().body("welcome!") -/// }))) -/// ).await; -/// -/// let req = test::TestRequest::post() -/// .uri("/index.html") -/// .header(header::CONTENT_TYPE, "application/json") -/// .to_request(); -/// -/// let resp = test::call_service(&app, req).await; -/// let result = test::read_body(resp).await; -/// assert_eq!(result, Bytes::from_static(b"welcome!")); -/// } -/// ``` -pub async fn read_body(res: ServiceResponse) -> Bytes -where - B: MessageBody + Unpin, - B::Error: Into, -{ - let body = res.into_body(); - let mut bytes = BytesMut::new(); - - actix_rt::pin!(body); - while let Some(item) = poll_fn(|cx| body.as_mut().poll_next(cx)).await { - bytes.extend_from_slice(&item.map_err(Into::into).unwrap()); - } - - bytes.freeze() -} - -/// Helper function that returns a deserialized response body of a ServiceResponse. -/// -/// ``` -/// use actix_web::{App, test, web, HttpResponse, http::header}; -/// use serde::{Serialize, Deserialize}; -/// -/// #[derive(Serialize, Deserialize)] -/// pub struct Person { -/// id: String, -/// name: String, -/// } -/// -/// #[actix_web::test] -/// async fn test_post_person() { -/// let app = test::init_service( -/// App::new().service( -/// web::resource("/people") -/// .route(web::post().to(|person: web::Json| async { -/// HttpResponse::Ok() -/// .json(person)}) -/// )) -/// ).await; -/// -/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); -/// -/// let resp = test::TestRequest::post() -/// .uri("/people") -/// .header(header::CONTENT_TYPE, "application/json") -/// .set_payload(payload) -/// .send_request(&mut app) -/// .await; -/// -/// assert!(resp.status().is_success()); -/// -/// let result: Person = test::read_body_json(resp).await; -/// } -/// ``` -pub async fn read_body_json(res: ServiceResponse) -> T -where - B: MessageBody + Unpin, - B::Error: Into, - T: DeserializeOwned, -{ - let body = read_body(res).await; - - serde_json::from_slice(&body).unwrap_or_else(|e| { - panic!( - "read_response_json failed during deserialization of body: {:?}, {}", - body, e - ) - }) -} - -pub async fn load_stream(mut stream: S) -> Result -where - S: Stream> + Unpin, -{ - let mut data = BytesMut::new(); - while let Some(item) = stream.next().await { - data.extend_from_slice(&item?); - } - Ok(data.freeze()) -} - -pub async fn load_body(body: B) -> Result -where - B: MessageBody + Unpin, - B::Error: Into, -{ - body::to_bytes(body).await.map_err(Into::into) -} - -/// Helper function that returns a deserialized response body of a TestRequest -/// -/// ``` -/// use actix_web::{App, test, web, HttpResponse, http::header}; -/// use serde::{Serialize, Deserialize}; -/// -/// #[derive(Serialize, Deserialize)] -/// pub struct Person { -/// id: String, -/// name: String -/// } -/// -/// #[actix_web::test] -/// async fn test_add_person() { -/// let app = test::init_service( -/// App::new().service( -/// web::resource("/people") -/// .route(web::post().to(|person: web::Json| async { -/// HttpResponse::Ok() -/// .json(person)}) -/// )) -/// ).await; -/// -/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); -/// -/// let req = test::TestRequest::post() -/// .uri("/people") -/// .header(header::CONTENT_TYPE, "application/json") -/// .set_payload(payload) -/// .to_request(); -/// -/// let result: Person = test::read_response_json(&mut app, req).await; -/// } -/// ``` -pub async fn read_response_json(app: &S, req: Request) -> T -where - S: Service, Error = Error>, - B: MessageBody + Unpin, - B::Error: Into, - T: DeserializeOwned, -{ - let body = read_response(app, req).await; - - serde_json::from_slice(&body).unwrap_or_else(|_| { - panic!( - "read_response_json failed during deserialization of body: {:?}", - body - ) - }) -} - -/// Test `Request` builder. -/// -/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. -/// You can generate various types of request via TestRequest's methods: -/// * `TestRequest::to_request` creates `actix_http::Request` instance. -/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. -/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. -/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. -/// -/// ``` -/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; -/// use actix_web::http::{header, StatusCode}; -/// -/// async fn index(req: HttpRequest) -> HttpResponse { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// HttpResponse::Ok().into() -/// } else { -/// HttpResponse::BadRequest().into() -/// } -/// } -/// -/// #[actix_web::test] -/// async fn test_index() { -/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") -/// .to_http_request(); -/// -/// let resp = index(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// } -/// ``` -pub struct TestRequest { - req: HttpTestRequest, - rmap: ResourceMap, - config: AppConfig, - path: Path, - peer_addr: Option, - app_data: Extensions, - #[cfg(feature = "cookies")] - cookies: CookieJar, -} - -impl Default for TestRequest { - fn default() -> TestRequest { - TestRequest { - req: HttpTestRequest::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - config: AppConfig::default(), - path: Path::new(Url::new(Uri::default())), - peer_addr: None, - app_data: Extensions::new(), - #[cfg(feature = "cookies")] - cookies: CookieJar::new(), - } - } -} - -#[allow(clippy::wrong_self_convention)] -impl TestRequest { - /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest { - TestRequest::default().uri(path) - } - - /// Create TestRequest and set method to `Method::GET` - pub fn get() -> TestRequest { - TestRequest::default().method(Method::GET) - } - - /// Create TestRequest and set method to `Method::POST` - pub fn post() -> TestRequest { - TestRequest::default().method(Method::POST) - } - - /// Create TestRequest and set method to `Method::PUT` - pub fn put() -> TestRequest { - TestRequest::default().method(Method::PUT) - } - - /// Create TestRequest and set method to `Method::PATCH` - pub fn patch() -> TestRequest { - TestRequest::default().method(Method::PATCH) - } - - /// Create TestRequest and set method to `Method::DELETE` - pub fn delete() -> TestRequest { - TestRequest::default().method(Method::DELETE) - } - - /// Set HTTP version of this request - pub fn version(mut self, ver: Version) -> Self { - self.req.version(ver); - self - } - - /// Set HTTP method of this request - pub fn method(mut self, meth: Method) -> Self { - self.req.method(meth); - self - } - - /// Set HTTP Uri of this request - pub fn uri(mut self, path: &str) -> Self { - self.req.uri(path); - self - } - - /// Insert a header, replacing any that were set with an equivalent field name. - pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { - self.req.insert_header(header); - self - } - - /// Append a header, keeping any that were set with an equivalent field name. - pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { - self.req.append_header(header); - self - } - - /// Set cookie for this request. - #[cfg(feature = "cookies")] - pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - self.cookies.add(cookie.into_owned()); - self - } - - /// Set request path pattern parameter. - /// - /// # Examples - /// ``` - /// use actix_web::test::TestRequest; - /// - /// let req = TestRequest::default().param("foo", "bar"); - /// let req = TestRequest::default().param("foo".to_owned(), "bar".to_owned()); - /// ``` - pub fn param( - mut self, - name: impl Into>, - value: impl Into>, - ) -> Self { - self.path.add_static(name, value); - self - } - - /// Set peer addr. - pub fn peer_addr(mut self, addr: SocketAddr) -> Self { - self.peer_addr = Some(addr); - self - } - - /// Set request payload. - pub fn set_payload>(mut self, data: B) -> Self { - self.req.set_payload(data); - self - } - - /// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type` - /// header is set to `application/x-www-form-urlencoded`. - pub fn set_form(mut self, data: &T) -> Self { - let bytes = serde_urlencoded::to_string(data) - .expect("Failed to serialize test data as a urlencoded form"); - self.req.set_payload(bytes); - self.req.insert_header(ContentType::form_url_encoded()); - self - } - - /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is - /// set to `application/json`. - pub fn set_json(mut self, data: &T) -> Self { - let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); - self.req.set_payload(bytes); - self.req.insert_header(ContentType::json()); - self - } - - /// Set application data. This is equivalent of `App::data()` method - /// for testing purpose. - pub fn data(mut self, data: T) -> Self { - self.app_data.insert(Data::new(data)); - self - } - - /// Set application data. This is equivalent of `App::app_data()` method - /// for testing purpose. - pub fn app_data(mut self, data: T) -> Self { - self.app_data.insert(data); - self - } - - #[cfg(test)] - /// Set request config - pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { - self.rmap = rmap; - self - } - - fn finish(&mut self) -> Request { - // mut used when cookie feature is enabled - #[allow(unused_mut)] - let mut req = self.req.finish(); - - #[cfg(feature = "cookies")] - { - use actix_http::header::{HeaderValue, COOKIE}; - - let cookie: String = self - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| c.stripped().encoded().to_string()) - .collect::>() - .join("; "); - - if !cookie.is_empty() { - req.headers_mut() - .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap()); - } - } - - req - } - - /// Complete request creation and generate `Request` instance - pub fn to_request(mut self) -> Request { - let mut req = self.finish(); - req.head_mut().peer_addr = self.peer_addr; - req - } - - /// Complete request creation and generate `ServiceRequest` instance - pub fn to_srv_request(mut self) -> ServiceRequest { - let (mut head, payload) = self.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - - ServiceRequest::new( - HttpRequest::new( - self.path, - head, - app_state, - Rc::new(self.app_data), - None, - Default::default(), - ), - payload, - ) - } - - /// Complete request creation and generate `ServiceResponse` instance - pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse { - self.to_srv_request().into_response(res) - } - - /// Complete request creation and generate `HttpRequest` instance - pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, _) = self.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - - HttpRequest::new( - self.path, - head, - app_state, - Rc::new(self.app_data), - None, - Default::default(), - ) - } - - /// Complete request creation and generate `HttpRequest` and `Payload` instances - pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let (mut head, payload) = self.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - - let req = HttpRequest::new( - self.path, - head, - app_state, - Rc::new(self.app_data), - None, - Default::default(), - ); - - (req, payload) - } - - /// Complete request creation, calls service and waits for response future completion. - pub async fn send_request(self, app: &S) -> S::Response - where - S: Service, Error = E>, - E: std::fmt::Debug, - { - let req = self.to_request(); - call_service(app, req).await - } - - #[cfg(test)] - pub fn set_server_hostname(&mut self, host: &str) { - self.config.set_host(host) - } -} - -/// Reduces boilerplate code when testing expected response payloads. -#[cfg(test)] -macro_rules! assert_body_eq { - ($res:ident, $expected:expr) => { - assert_eq!( - ::actix_http::body::to_bytes($res.into_body()) - .await - .expect("body read should have succeeded"), - Bytes::from_static($expected), - ) - }; -} - -#[cfg(test)] -pub(crate) use assert_body_eq; - -#[cfg(test)] -mod tests { - use std::time::SystemTime; - - use actix_http::HttpMessage; - use serde::{Deserialize, Serialize}; - - use super::*; - use crate::{http::header, web, App, HttpResponse, Responder}; - - #[actix_rt::test] - async fn test_basics() { - let req = TestRequest::default() - .version(Version::HTTP_2) - .insert_header(header::ContentType::json()) - .insert_header(header::Date(SystemTime::now().into())) - .param("test", "123") - .data(10u32) - .app_data(20u64) - .peer_addr("127.0.0.1:8081".parse().unwrap()) - .to_http_request(); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - assert!(req.headers().contains_key(header::DATE)); - assert_eq!( - req.head().peer_addr, - Some("127.0.0.1:8081".parse().unwrap()) - ); - assert_eq!(&req.match_info()["test"], "123"); - assert_eq!(req.version(), Version::HTTP_2); - let data = req.app_data::>().unwrap(); - assert!(req.app_data::>().is_none()); - assert_eq!(*data.get_ref(), 10); - - assert!(req.app_data::().is_none()); - let data = req.app_data::().unwrap(); - assert_eq!(*data, 20); - } - - #[actix_rt::test] - async fn test_request_methods() { - let app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::put().to(|| HttpResponse::Ok().body("put!"))) - .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) - .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), - ), - ) - .await; - - let put_req = TestRequest::put() - .uri("/index.html") - .insert_header((header::CONTENT_TYPE, "application/json")) - .to_request(); - - let result = read_response(&app, put_req).await; - assert_eq!(result, Bytes::from_static(b"put!")); - - let patch_req = TestRequest::patch() - .uri("/index.html") - .insert_header((header::CONTENT_TYPE, "application/json")) - .to_request(); - - 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(&app, delete_req).await; - assert_eq!(result, Bytes::from_static(b"delete!")); - } - - #[actix_rt::test] - async fn test_response() { - let app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), - ), - ) - .await; - - let req = TestRequest::post() - .uri("/index.html") - .insert_header((header::CONTENT_TYPE, "application/json")) - .to_request(); - - let result = read_response(&app, req).await; - assert_eq!(result, Bytes::from_static(b"welcome!")); - } - - #[actix_rt::test] - async fn test_send_request() { - let app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))), - ), - ) - .await; - - let resp = TestRequest::get() - .uri("/index.html") - .send_request(&app) - .await; - - let result = read_body(resp).await; - assert_eq!(result, Bytes::from_static(b"welcome!")); - } - - #[derive(Serialize, Deserialize)] - pub struct Person { - id: String, - name: String, - } - - #[actix_rt::test] - async fn test_response_json() { - let app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), - ))) - .await; - - let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); - - let req = TestRequest::post() - .uri("/people") - .insert_header((header::CONTENT_TYPE, "application/json")) - .set_payload(payload) - .to_request(); - - let result: Person = read_response_json(&app, req).await; - assert_eq!(&result.id, "12345"); - } - - #[actix_rt::test] - async fn test_body_json() { - let app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), - ))) - .await; - - let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); - - let resp = TestRequest::post() - .uri("/people") - .insert_header((header::CONTENT_TYPE, "application/json")) - .set_payload(payload) - .send_request(&app) - .await; - - let result: Person = read_body_json(resp).await; - assert_eq!(&result.name, "User name"); - } - - #[actix_rt::test] - async fn test_request_response_form() { - let app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), - ))) - .await; - - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; - - let req = TestRequest::post() - .uri("/people") - .set_form(&payload) - .to_request(); - - assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - - 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 app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), - ))) - .await; - - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; - - let req = TestRequest::post() - .uri("/people") - .set_json(&payload) - .to_request(); - - assert_eq!(req.content_type(), "application/json"); - - 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_async_with_block() { - async fn async_with_block() -> Result { - let res = web::block(move || Some(4usize).ok_or("wrong")).await; - - match res { - Ok(value) => Ok(HttpResponse::Ok() - .content_type("text/plain") - .body(format!("Async with block value: {:?}", value))), - Err(_) => panic!("Unexpected"), - } - } - - let app = - init_service(App::new().service(web::resource("/index.html").to(async_with_block))) - .await; - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } - - // allow deprecated App::data - #[allow(deprecated)] - #[actix_rt::test] - async fn test_server_data() { - async fn handler(data: web::Data) -> impl Responder { - assert_eq!(**data, 10); - HttpResponse::Ok() - } - - let app = init_service( - App::new() - .data(10usize) - .service(web::resource("/index.html").to(handler)), - ) - .await; - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } -} diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 000000000..a29dfc437 --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,81 @@ +//! Various helpers for Actix applications to use during testing. +//! +//! # Creating A Test Service +//! - [`init_service`] +//! +//! # Off-The-Shelf Test Services +//! - [`ok_service`] +//! - [`simple_service`] +//! +//! # Calling Test Service +//! - [`TestRequest`] +//! - [`call_service`] +//! - [`call_and_read_body`] +//! - [`call_and_read_body_json`] +//! +//! # Reading Response Payloads +//! - [`read_body`] +//! - [`read_body_json`] + +// TODO: more docs on generally how testing works with these parts + +pub use actix_http::test::TestBuffer; + +mod test_request; +mod test_services; +mod test_utils; + +pub use self::test_request::TestRequest; +#[allow(deprecated)] +pub use self::test_services::{default_service, ok_service, simple_service}; +#[allow(deprecated)] +pub use self::test_utils::{ + call_and_read_body, call_and_read_body_json, call_service, init_service, read_body, + read_body_json, read_response, read_response_json, +}; + +#[cfg(test)] +pub(crate) use self::test_utils::try_init_service; + +/// Reduces boilerplate code when testing expected response payloads. +/// +/// Must be used inside an async test. Works for both `ServiceRequest` and `HttpRequest`. +/// +/// # Examples +/// ``` +/// use actix_web::{http::StatusCode, HttpResponse}; +/// +/// let res = HttpResponse::with_body(StatusCode::OK, "http response"); +/// assert_body_eq!(res, b"http response"); +/// ``` +#[cfg(test)] +macro_rules! assert_body_eq { + ($res:ident, $expected:expr) => { + assert_eq!( + ::actix_http::body::to_bytes($res.into_body()) + .await + .expect("error reading test response body"), + ::bytes::Bytes::from_static($expected), + ) + }; +} + +#[cfg(test)] +pub(crate) use assert_body_eq; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{http::StatusCode, service::ServiceResponse, HttpResponse}; + + #[actix_rt::test] + async fn assert_body_works_for_service_and_regular_response() { + let res = HttpResponse::with_body(StatusCode::OK, "http response"); + assert_body_eq!(res, b"http response"); + + let req = TestRequest::default().to_http_request(); + let res = HttpResponse::with_body(StatusCode::OK, "service response"); + let res = ServiceResponse::new(req, res); + assert_body_eq!(res, b"service response"); + } +} diff --git a/src/test/test_request.rs b/src/test/test_request.rs new file mode 100644 index 000000000..fd3355ef3 --- /dev/null +++ b/src/test/test_request.rs @@ -0,0 +1,431 @@ +use std::{borrow::Cow, net::SocketAddr, rc::Rc}; + +use actix_http::{test::TestRequest as HttpTestRequest, Request}; +use serde::Serialize; + +use crate::{ + app_service::AppInitServiceState, + config::AppConfig, + data::Data, + dev::{Extensions, Path, Payload, ResourceDef, Service, Url}, + http::header::ContentType, + http::{header::TryIntoHeaderPair, Method, Uri, Version}, + rmap::ResourceMap, + service::{ServiceRequest, ServiceResponse}, + test, + web::Bytes, + HttpRequest, HttpResponse, +}; + +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, CookieJar}; + +/// Test `Request` builder. +/// +/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. +/// You can generate various types of request via TestRequest's methods: +/// * `TestRequest::to_request` creates `actix_http::Request` instance. +/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. +/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. +/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. +/// +/// ``` +/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; +/// use actix_web::http::{header, StatusCode}; +/// +/// async fn index(req: HttpRequest) -> HttpResponse { +/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { +/// HttpResponse::Ok().into() +/// } else { +/// HttpResponse::BadRequest().into() +/// } +/// } +/// +/// #[actix_web::test] +/// async fn test_index() { +/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") +/// .to_http_request(); +/// +/// let resp = index(req).await.unwrap(); +/// assert_eq!(resp.status(), StatusCode::OK); +/// +/// let req = test::TestRequest::default().to_http_request(); +/// let resp = index(req).await.unwrap(); +/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); +/// } +/// ``` +pub struct TestRequest { + req: HttpTestRequest, + rmap: ResourceMap, + config: AppConfig, + path: Path, + peer_addr: Option, + app_data: Extensions, + #[cfg(feature = "cookies")] + cookies: CookieJar, +} + +impl Default for TestRequest { + fn default() -> TestRequest { + TestRequest { + req: HttpTestRequest::default(), + rmap: ResourceMap::new(ResourceDef::new("")), + config: AppConfig::default(), + path: Path::new(Url::new(Uri::default())), + peer_addr: None, + app_data: Extensions::new(), + #[cfg(feature = "cookies")] + cookies: CookieJar::new(), + } + } +} + +#[allow(clippy::wrong_self_convention)] +impl TestRequest { + /// Create TestRequest and set request uri + pub fn with_uri(path: &str) -> TestRequest { + TestRequest::default().uri(path) + } + + /// Create TestRequest and set method to `Method::GET` + pub fn get() -> TestRequest { + TestRequest::default().method(Method::GET) + } + + /// Create TestRequest and set method to `Method::POST` + pub fn post() -> TestRequest { + TestRequest::default().method(Method::POST) + } + + /// Create TestRequest and set method to `Method::PUT` + pub fn put() -> TestRequest { + TestRequest::default().method(Method::PUT) + } + + /// Create TestRequest and set method to `Method::PATCH` + pub fn patch() -> TestRequest { + TestRequest::default().method(Method::PATCH) + } + + /// Create TestRequest and set method to `Method::DELETE` + pub fn delete() -> TestRequest { + TestRequest::default().method(Method::DELETE) + } + + /// Set HTTP version of this request + pub fn version(mut self, ver: Version) -> Self { + self.req.version(ver); + self + } + + /// Set HTTP method of this request + pub fn method(mut self, meth: Method) -> Self { + self.req.method(meth); + self + } + + /// Set HTTP Uri of this request + pub fn uri(mut self, path: &str) -> Self { + self.req.uri(path); + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { + self.req.insert_header(header); + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { + self.req.append_header(header); + self + } + + /// Set cookie for this request. + #[cfg(feature = "cookies")] + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { + self.cookies.add(cookie.into_owned()); + self + } + + /// Set request path pattern parameter. + /// + /// # Examples + /// ``` + /// use actix_web::test::TestRequest; + /// + /// let req = TestRequest::default().param("foo", "bar"); + /// let req = TestRequest::default().param("foo".to_owned(), "bar".to_owned()); + /// ``` + pub fn param( + mut self, + name: impl Into>, + value: impl Into>, + ) -> Self { + self.path.add_static(name, value); + self + } + + /// Set peer addr. + pub fn peer_addr(mut self, addr: SocketAddr) -> Self { + self.peer_addr = Some(addr); + self + } + + /// Set request payload. + pub fn set_payload>(mut self, data: B) -> Self { + self.req.set_payload(data); + self + } + + /// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type` + /// header is set to `application/x-www-form-urlencoded`. + pub fn set_form(mut self, data: &T) -> Self { + let bytes = serde_urlencoded::to_string(data) + .expect("Failed to serialize test data as a urlencoded form"); + self.req.set_payload(bytes); + self.req.insert_header(ContentType::form_url_encoded()); + self + } + + /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is + /// set to `application/json`. + pub fn set_json(mut self, data: &T) -> Self { + let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); + self.req.set_payload(bytes); + self.req.insert_header(ContentType::json()); + self + } + + /// Set application data. This is equivalent of `App::data()` method + /// for testing purpose. + pub fn data(mut self, data: T) -> Self { + self.app_data.insert(Data::new(data)); + self + } + + /// Set application data. This is equivalent of `App::app_data()` method + /// for testing purpose. + pub fn app_data(mut self, data: T) -> Self { + self.app_data.insert(data); + self + } + + #[cfg(test)] + /// Set request config + pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { + self.rmap = rmap; + self + } + + fn finish(&mut self) -> Request { + // mut used when cookie feature is enabled + #[allow(unused_mut)] + let mut req = self.req.finish(); + + #[cfg(feature = "cookies")] + { + use actix_http::header::{HeaderValue, COOKIE}; + + let cookie: String = self + .cookies + .delta() + // ensure only name=value is written to cookie header + .map(|c| c.stripped().encoded().to_string()) + .collect::>() + .join("; "); + + if !cookie.is_empty() { + req.headers_mut() + .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap()); + } + } + + req + } + + /// Complete request creation and generate `Request` instance + pub fn to_request(mut self) -> Request { + let mut req = self.finish(); + req.head_mut().peer_addr = self.peer_addr; + req + } + + /// Complete request creation and generate `ServiceRequest` instance + pub fn to_srv_request(mut self) -> ServiceRequest { + let (mut head, payload) = self.finish().into_parts(); + head.peer_addr = self.peer_addr; + self.path.get_mut().update(&head.uri); + + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + + ServiceRequest::new( + HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ), + payload, + ) + } + + /// Complete request creation and generate `ServiceResponse` instance + pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse { + self.to_srv_request().into_response(res) + } + + /// Complete request creation and generate `HttpRequest` instance + pub fn to_http_request(mut self) -> HttpRequest { + let (mut head, _) = self.finish().into_parts(); + head.peer_addr = self.peer_addr; + self.path.get_mut().update(&head.uri); + + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + + HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ) + } + + /// Complete request creation and generate `HttpRequest` and `Payload` instances + pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { + let (mut head, payload) = self.finish().into_parts(); + head.peer_addr = self.peer_addr; + self.path.get_mut().update(&head.uri); + + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + + let req = HttpRequest::new( + self.path, + head, + app_state, + Rc::new(self.app_data), + None, + Default::default(), + ); + + (req, payload) + } + + /// Complete request creation, calls service and waits for response future completion. + pub async fn send_request(self, app: &S) -> S::Response + where + S: Service, Error = E>, + E: std::fmt::Debug, + { + let req = self.to_request(); + test::call_service(app, req).await + } + + #[cfg(test)] + pub fn set_server_hostname(&mut self, host: &str) { + self.config.set_host(host) + } +} + +#[cfg(test)] +mod tests { + use std::time::SystemTime; + + use super::*; + use crate::{http::header, test::init_service, web, App, Error, HttpResponse, Responder}; + + #[actix_rt::test] + async fn test_basics() { + let req = TestRequest::default() + .version(Version::HTTP_2) + .insert_header(header::ContentType::json()) + .insert_header(header::Date(SystemTime::now().into())) + .param("test", "123") + .data(10u32) + .app_data(20u64) + .peer_addr("127.0.0.1:8081".parse().unwrap()) + .to_http_request(); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + assert!(req.headers().contains_key(header::DATE)); + assert_eq!( + req.head().peer_addr, + Some("127.0.0.1:8081".parse().unwrap()) + ); + assert_eq!(&req.match_info()["test"], "123"); + assert_eq!(req.version(), Version::HTTP_2); + let data = req.app_data::>().unwrap(); + assert!(req.app_data::>().is_none()); + assert_eq!(*data.get_ref(), 10); + + assert!(req.app_data::().is_none()); + let data = req.app_data::().unwrap(); + assert_eq!(*data, 20); + } + + #[actix_rt::test] + async fn test_send_request() { + let app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))), + ), + ) + .await; + + let resp = TestRequest::get() + .uri("/index.html") + .send_request(&app) + .await; + + let result = test::read_body(resp).await; + assert_eq!(result, Bytes::from_static(b"welcome!")); + } + + #[actix_rt::test] + async fn test_async_with_block() { + async fn async_with_block() -> Result { + let res = web::block(move || Some(4usize).ok_or("wrong")).await; + + match res { + Ok(value) => Ok(HttpResponse::Ok() + .content_type("text/plain") + .body(format!("Async with block value: {:?}", value))), + Err(_) => panic!("Unexpected"), + } + } + + let app = + init_service(App::new().service(web::resource("/index.html").to(async_with_block))) + .await; + + let req = TestRequest::post().uri("/index.html").to_request(); + let res = app.call(req).await.unwrap(); + assert!(res.status().is_success()); + } + + // allow deprecated App::data + #[allow(deprecated)] + #[actix_rt::test] + async fn test_server_data() { + async fn handler(data: web::Data) -> impl Responder { + assert_eq!(**data, 10); + HttpResponse::Ok() + } + + let app = init_service( + App::new() + .data(10usize) + .service(web::resource("/index.html").to(handler)), + ) + .await; + + let req = TestRequest::post().uri("/index.html").to_request(); + let res = app.call(req).await.unwrap(); + assert!(res.status().is_success()); + } +} diff --git a/src/test/test_services.rs b/src/test/test_services.rs new file mode 100644 index 000000000..b4810cfd8 --- /dev/null +++ b/src/test/test_services.rs @@ -0,0 +1,31 @@ +use actix_utils::future::ok; + +use crate::{ + body::BoxBody, + dev::{fn_service, Service, ServiceRequest, ServiceResponse}, + http::StatusCode, + Error, HttpResponseBuilder, +}; + +/// Creates service that always responds with `200 OK` and no body. +pub fn ok_service( +) -> impl Service, Error = Error> { + simple_service(StatusCode::OK) +} + +/// Creates service that always responds with given status code and no body. +pub fn simple_service( + status_code: StatusCode, +) -> impl Service, Error = Error> { + fn_service(move |req: ServiceRequest| { + ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) + }) +} + +#[doc(hidden)] +#[deprecated(since = "4.0.0", note = "Renamed to `simple_service`.")] +pub fn default_service( + status_code: StatusCode, +) -> impl Service, Error = Error> { + simple_service(status_code) +} diff --git a/src/test/test_utils.rs b/src/test/test_utils.rs new file mode 100644 index 000000000..02d4c9bf3 --- /dev/null +++ b/src/test/test_utils.rs @@ -0,0 +1,474 @@ +use std::fmt; + +use actix_http::Request; +use actix_service::IntoServiceFactory; +use serde::de::DeserializeOwned; + +use crate::{ + body::{self, MessageBody}, + config::AppConfig, + dev::{Service, ServiceFactory}, + service::ServiceResponse, + web::Bytes, + Error, +}; + +/// Initialize service from application builder instance. +/// +/// # Examples +/// ``` +/// use actix_service::Service; +/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; +/// +/// #[actix_web::test] +/// async fn test_init_service() { +/// let app = test::init_service( +/// App::new() +/// .service(web::resource("/test").to(|| async { "OK" })) +/// ).await; +/// +/// // Create request object +/// let req = test::TestRequest::with_uri("/test").to_request(); +/// +/// // Execute application +/// let res = app.call(req).await.unwrap(); +/// assert_eq!(res.status(), StatusCode::OK); +/// } +/// ``` +/// +/// # Panics +/// Panics if service initialization returns an error. +pub async fn init_service( + app: R, +) -> impl Service, Error = E> +where + R: IntoServiceFactory, + S: ServiceFactory, Error = E>, + S::InitError: std::fmt::Debug, +{ + try_init_service(app) + .await + .expect("service initialization failed") +} + +/// Fallible version of [`init_service`] that allows testing initialization errors. +pub(crate) async fn try_init_service( + app: R, +) -> Result, Error = E>, S::InitError> +where + R: IntoServiceFactory, + S: ServiceFactory, Error = E>, + S::InitError: std::fmt::Debug, +{ + let srv = app.into_factory(); + srv.new_service(AppConfig::default()).await +} + +/// Calls service and waits for response future completion. +/// +/// # Examples +/// ``` +/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; +/// +/// #[actix_web::test] +/// async fn test_response() { +/// let app = test::init_service( +/// App::new() +/// .service(web::resource("/test").to(|| async { +/// HttpResponse::Ok() +/// })) +/// ).await; +/// +/// // Create request object +/// let req = test::TestRequest::with_uri("/test").to_request(); +/// +/// // Call application +/// let res = test::call_service(&app, req).await; +/// assert_eq!(res.status(), StatusCode::OK); +/// } +/// ``` +/// +/// # Panics +/// Panics if service call returns error. +pub async fn call_service(app: &S, req: R) -> S::Response +where + S: Service, Error = E>, + E: std::fmt::Debug, +{ + app.call(req) + .await + .expect("test service call returned error") +} + +/// Helper function that returns a response body of a TestRequest +/// +/// # Examples +/// ``` +/// use actix_web::{test, web, App, HttpResponse, http::header}; +/// use bytes::Bytes; +/// +/// #[actix_web::test] +/// async fn test_index() { +/// let app = test::init_service( +/// App::new().service( +/// web::resource("/index.html") +/// .route(web::post().to(|| async { +/// HttpResponse::Ok().body("welcome!") +/// }))) +/// ).await; +/// +/// let req = test::TestRequest::post() +/// .uri("/index.html") +/// .header(header::CONTENT_TYPE, "application/json") +/// .to_request(); +/// +/// let result = test::call_and_read_body(&app, req).await; +/// assert_eq!(result, Bytes::from_static(b"welcome!")); +/// } +/// ``` +/// +/// # Panics +/// Panics if: +/// - service call returns error; +/// - body yields an error while it is being read. +pub async fn call_and_read_body(app: &S, req: Request) -> Bytes +where + S: Service, Error = Error>, + B: MessageBody, + B::Error: fmt::Debug, +{ + let res = call_service(app, req).await; + read_body(res).await +} + +#[doc(hidden)] +#[deprecated(since = "4.0.0", note = "Renamed to `call_and_read_body`.")] +pub async fn read_response(app: &S, req: Request) -> Bytes +where + S: Service, Error = Error>, + B: MessageBody, + B::Error: fmt::Debug, +{ + let res = call_service(app, req).await; + read_body(res).await +} + +/// Helper function that returns a response body of a ServiceResponse. +/// +/// # Examples +/// ``` +/// use actix_web::{test, web, App, HttpResponse, http::header}; +/// use bytes::Bytes; +/// +/// #[actix_web::test] +/// async fn test_index() { +/// let app = test::init_service( +/// App::new().service( +/// web::resource("/index.html") +/// .route(web::post().to(|| async { +/// HttpResponse::Ok().body("welcome!") +/// }))) +/// ).await; +/// +/// let req = test::TestRequest::post() +/// .uri("/index.html") +/// .header(header::CONTENT_TYPE, "application/json") +/// .to_request(); +/// +/// let res = test::call_service(&app, req).await; +/// let result = test::read_body(res).await; +/// assert_eq!(result, Bytes::from_static(b"welcome!")); +/// } +/// ``` +/// +/// # Panics +/// Panics if body yields an error while it is being read. +pub async fn read_body(res: ServiceResponse) -> Bytes +where + B: MessageBody, + B::Error: fmt::Debug, +{ + let body = res.into_body(); + body::to_bytes(body) + .await + .expect("error reading test response body") +} + +/// Helper function that returns a deserialized response body of a ServiceResponse. +/// +/// # Examples +/// ``` +/// use actix_web::{App, test, web, HttpResponse, http::header}; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// pub struct Person { +/// id: String, +/// name: String, +/// } +/// +/// #[actix_web::test] +/// async fn test_post_person() { +/// let app = test::init_service( +/// App::new().service( +/// web::resource("/people") +/// .route(web::post().to(|person: web::Json| async { +/// HttpResponse::Ok() +/// .json(person)}) +/// )) +/// ).await; +/// +/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); +/// +/// let res = test::TestRequest::post() +/// .uri("/people") +/// .header(header::CONTENT_TYPE, "application/json") +/// .set_payload(payload) +/// .send_request(&mut app) +/// .await; +/// +/// assert!(res.status().is_success()); +/// +/// let result: Person = test::read_body_json(res).await; +/// } +/// ``` +/// +/// # Panics +/// Panics if: +/// - body yields an error while it is being read; +/// - received body is not a valid JSON representation of `T`. +pub async fn read_body_json(res: ServiceResponse) -> T +where + B: MessageBody, + B::Error: fmt::Debug, + T: DeserializeOwned, +{ + let body = read_body(res).await; + + serde_json::from_slice(&body).unwrap_or_else(|err| { + panic!( + "could not deserialize body into a {}\nerr: {}\nbody: {:?}", + std::any::type_name::(), + err, + body, + ) + }) +} + +/// Helper function that returns a deserialized response body of a TestRequest +/// +/// # Examples +/// ``` +/// use actix_web::{App, test, web, HttpResponse, http::header}; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// pub struct Person { +/// id: String, +/// name: String +/// } +/// +/// #[actix_web::test] +/// async fn test_add_person() { +/// let app = test::init_service( +/// App::new().service( +/// web::resource("/people") +/// .route(web::post().to(|person: web::Json| async { +/// HttpResponse::Ok() +/// .json(person)}) +/// )) +/// ).await; +/// +/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); +/// +/// let req = test::TestRequest::post() +/// .uri("/people") +/// .header(header::CONTENT_TYPE, "application/json") +/// .set_payload(payload) +/// .to_request(); +/// +/// let result: Person = test::call_and_read_body_json(&mut app, req).await; +/// } +/// ``` +/// +/// # Panics +/// Panics if: +/// - service call returns an error body yields an error while it is being read; +/// - body yields an error while it is being read; +/// - received body is not a valid JSON representation of `T`. +pub async fn call_and_read_body_json(app: &S, req: Request) -> T +where + S: Service, Error = Error>, + B: MessageBody, + B::Error: fmt::Debug, + T: DeserializeOwned, +{ + let res = call_service(app, req).await; + read_body_json(res).await +} + +#[doc(hidden)] +#[deprecated(since = "4.0.0", note = "Renamed to `call_and_read_body_json`.")] +pub async fn read_response_json(app: &S, req: Request) -> T +where + S: Service, Error = Error>, + B: MessageBody, + B::Error: fmt::Debug, + T: DeserializeOwned, +{ + call_and_read_body_json(app, req).await +} + +#[cfg(test)] +mod tests { + + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::{http::header, test::TestRequest, web, App, HttpMessage, HttpResponse}; + + #[actix_rt::test] + async fn test_request_methods() { + let app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::put().to(|| HttpResponse::Ok().body("put!"))) + .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) + .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), + ), + ) + .await; + + let put_req = TestRequest::put() + .uri("/index.html") + .insert_header((header::CONTENT_TYPE, "application/json")) + .to_request(); + + let result = call_and_read_body(&app, put_req).await; + assert_eq!(result, Bytes::from_static(b"put!")); + + let patch_req = TestRequest::patch() + .uri("/index.html") + .insert_header((header::CONTENT_TYPE, "application/json")) + .to_request(); + + let result = call_and_read_body(&app, patch_req).await; + assert_eq!(result, Bytes::from_static(b"patch!")); + + let delete_req = TestRequest::delete().uri("/index.html").to_request(); + let result = call_and_read_body(&app, delete_req).await; + assert_eq!(result, Bytes::from_static(b"delete!")); + } + + #[derive(Serialize, Deserialize)] + pub struct Person { + id: String, + name: String, + } + + #[actix_rt::test] + async fn test_response_json() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + ))) + .await; + + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + + let req = TestRequest::post() + .uri("/people") + .insert_header((header::CONTENT_TYPE, "application/json")) + .set_payload(payload) + .to_request(); + + let result: Person = call_and_read_body_json(&app, req).await; + assert_eq!(&result.id, "12345"); + } + + #[actix_rt::test] + async fn test_body_json() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + ))) + .await; + + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + + let res = TestRequest::post() + .uri("/people") + .insert_header((header::CONTENT_TYPE, "application/json")) + .set_payload(payload) + .send_request(&app) + .await; + + let result: Person = read_body_json(res).await; + assert_eq!(&result.name, "User name"); + } + + #[actix_rt::test] + async fn test_request_response_form() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), + ))) + .await; + + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; + + let req = TestRequest::post() + .uri("/people") + .set_form(&payload) + .to_request(); + + assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); + + let result: Person = call_and_read_body_json(&app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + } + + #[actix_rt::test] + async fn test_response() { + let app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), + ), + ) + .await; + + let req = TestRequest::post() + .uri("/index.html") + .insert_header((header::CONTENT_TYPE, "application/json")) + .to_request(); + + let result = call_and_read_body(&app, req).await; + assert_eq!(result, Bytes::from_static(b"welcome!")); + } + + #[actix_rt::test] + async fn test_request_response_json() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + ))) + .await; + + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; + + let req = TestRequest::post() + .uri("/people") + .set_json(&payload) + .to_request(); + + assert_eq!(req.content_type(), "application/json"); + + let result: Person = call_and_read_body_json(&app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + } +} diff --git a/src/types/either.rs b/src/types/either.rs index 5b8e02525..0eafb9e43 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -20,8 +20,6 @@ use crate::{ /// Combines two extractor or responder types into a single type. /// -/// Can be converted to and from an [`either::Either`]. -/// /// # Extractor /// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for /// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. diff --git a/src/types/json.rs b/src/types/json.rs index 2b4d220e2..be6078b2b 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -449,12 +449,13 @@ mod tests { use super::*; use crate::{ + body, error::InternalError, http::{ header::{self, CONTENT_LENGTH, CONTENT_TYPE}, StatusCode, }, - test::{assert_body_eq, load_body, TestRequest}, + test::{assert_body_eq, TestRequest}, }; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -517,7 +518,7 @@ mod tests { let resp = HttpResponse::from_error(s.err().unwrap()); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let body = load_body(resp.into_body()).await.unwrap(); + let body = body::to_bytes(resp.into_body()).await.unwrap(); let msg: MyObject = serde_json::from_slice(&body).unwrap(); assert_eq!(msg.name, "invalid request"); } From a6d5776481eccf50819a2a64953ef4c954f1dcf9 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 17 Dec 2021 01:25:10 +0300 Subject: [PATCH 179/861] various fixes to MessageBody::complete_body (#2519) --- actix-http/src/body/boxed.rs | 22 +--------------- actix-http/src/body/message_body.rs | 39 ++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index d2469e986..d4737aab8 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -57,27 +57,7 @@ impl MessageBody for BoxBody { } fn take_complete_body(&mut self) -> Bytes { - debug_assert!( - self.is_complete_body(), - "boxed type does not allow taking complete body; caller should make sure to \ - call `is_complete_body` first", - ); - - // we do not have DerefMut access to call take_complete_body directly but since - // is_complete_body is true we should expect the entire bytes chunk in one poll_next - - let waker = futures_util::task::noop_waker(); - let mut cx = Context::from_waker(&waker); - - match self.as_pin_mut().poll_next(&mut cx) { - Poll::Ready(Some(Ok(data))) => data, - _ => { - panic!( - "boxed type indicated it allows taking complete body but failed to \ - return Bytes when polled", - ); - } - } + self.0.take_complete_body() } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 3e6c8d5cb..20263b3fb 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -134,7 +134,7 @@ mod foreign_impls { impl MessageBody for Box where - B: MessageBody + Unpin, + B: MessageBody + Unpin + ?Sized, { type Error = B::Error; @@ -164,7 +164,7 @@ mod foreign_impls { impl MessageBody for Pin> where - B: MessageBody, + B: MessageBody + ?Sized, { type Error = B::Error; @@ -175,10 +175,10 @@ mod foreign_impls { #[inline] fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - self.as_mut().poll_next(cx) + self.get_mut().as_mut().poll_next(cx) } #[inline] @@ -475,6 +475,16 @@ where None => Poll::Ready(None), } } + + #[inline] + fn is_complete_body(&self) -> bool { + self.body.is_complete_body() + } + + #[inline] + fn take_complete_body(&mut self) -> Bytes { + self.body.take_complete_body() + } } #[cfg(test)] @@ -630,6 +640,27 @@ mod tests { assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None)); } + #[test] + fn complete_body_combinators() { + use crate::body::{BoxBody, EitherBody}; + + let body = Bytes::from_static(b"test"); + let body = BoxBody::new(body); + let body = EitherBody::<_, ()>::left(body); + let body = EitherBody::<(), _>::right(body); + let body = Box::new(body); + let body = Box::pin(body); + let mut body = body; + + assert!(body.is_complete_body()); + assert_eq!(body.take_complete_body(), b"test".as_ref()); + + // subsequent poll_next returns None + let waker = futures_util::task::noop_waker(); + let mut cx = Context::from_waker(&waker); + assert!(Pin::new(&mut body).poll_next(&mut cx).map_err(drop) == Poll::Ready(None)); + } + // down-casting used to be done with a method on MessageBody trait // test is kept to demonstrate equivalence of Any trait #[actix_rt::test] From 44b7302845e163805fd12a445c34816d43cf98ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 16 Dec 2021 22:26:45 +0000 Subject: [PATCH 180/861] minimize futures-util dep in actix-http --- actix-http/Cargo.toml | 3 ++- actix-http/src/body/message_body.rs | 4 ++-- awc/Cargo.toml | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 374a55a62..2e8ec1dfc 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,7 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } +futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] } h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" @@ -89,6 +89,7 @@ actix-web = "4.0.0-beta.14" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" +futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } rcgen = "0.8" regex = "1.3" rustls-pemfile = "0.2" diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 20263b3fb..10a7260f4 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -198,7 +198,7 @@ mod foreign_impls { // we do not have DerefMut access to call take_complete_body directly but since // is_complete_body is true we should expect the entire bytes chunk in one poll_next - let waker = futures_util::task::noop_waker(); + let waker = futures_task::noop_waker(); let mut cx = Context::from_waker(&waker); match self.as_mut().poll_next(&mut cx) { @@ -631,7 +631,7 @@ mod tests { // second call returns empty assert_eq!(data.take_complete_body(), b"".as_ref()); - let waker = futures_util::task::noop_waker(); + let waker = futures_task::noop_waker(); let mut cx = Context::from_waker(&waker); let mut data = Bytes::from_static(b"test"); // take returns whole chunk diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 48ae27df0..60a95871c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -70,8 +70,8 @@ base64 = "0.13" bytes = "1" cfg-if = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" http = "0.2.5" itoa = "0.4" From 3c0d059d92aa06be9e3a5b9216977dd3b7572f91 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 17 Dec 2021 03:43:40 +0300 Subject: [PATCH 181/861] MessageBody::boxed (#2520) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 2 ++ actix-http/src/body/boxed.rs | 16 +++++++++++++++- actix-http/src/body/either.rs | 8 ++++++++ actix-http/src/body/message_body.rs | 13 +++++++++++-- actix-http/src/response.rs | 2 +- awc/src/any_body.rs | 4 +--- src/response/response.rs | 3 +-- src/service.rs | 2 +- 8 files changed, 40 insertions(+), 10 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 011e2c608..598ef9c0e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -28,6 +28,7 @@ * `Request::take_req_data()`. [#2487] * `impl Clone` for `RequestHead`. [#2487] * New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497] +* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -56,6 +57,7 @@ [#2488]: https://github.com/actix/actix-web/pull/2488 [#2491]: https://github.com/actix/actix-web/pull/2491 [#2497]: https://github.com/actix/actix-web/pull/2497 +[#2520]: https://github.com/actix/actix-web/pull/2520 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index d4737aab8..7581bec88 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -14,7 +14,11 @@ use crate::Error; pub struct BoxBody(Pin>>>); impl BoxBody { - /// Boxes a `MessageBody` and any errors it generates. + /// Same as `MessageBody::boxed`. + /// + /// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to + /// avoid double boxing. + #[inline] pub fn new(body: B) -> Self where B: MessageBody + 'static, @@ -24,6 +28,7 @@ impl BoxBody { } /// Returns a mutable pinned reference to the inner message body type. + #[inline] pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody>)> { self.0.as_mut() } @@ -38,10 +43,12 @@ impl fmt::Debug for BoxBody { impl MessageBody for BoxBody { type Error = Error; + #[inline] fn size(&self) -> BodySize { self.0.size() } + #[inline] fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -52,13 +59,20 @@ impl MessageBody for BoxBody { .map_err(|err| Error::new_body().with_cause(err)) } + #[inline] fn is_complete_body(&self) -> bool { self.0.is_complete_body() } + #[inline] fn take_complete_body(&mut self) -> Bytes { self.0.take_complete_body() } + + #[inline] + fn boxed(self) -> BoxBody { + self + } } #[cfg(test)] diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index 103b39c5d..3a4082dc9 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -88,6 +88,14 @@ where EitherBody::Right { body } => body.take_complete_body(), } } + + #[inline] + fn boxed(self) -> BoxBody { + match self { + EitherBody::Left { body } => body.boxed(), + EitherBody::Right { body } => body.boxed(), + } + } } #[cfg(test)] diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 10a7260f4..075ae7220 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -12,7 +12,7 @@ use bytes::{Bytes, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; -use super::BodySize; +use super::{BodySize, BoxBody}; /// An interface types that can converted to bytes and used as response bodies. // TODO: examples @@ -77,6 +77,15 @@ pub trait MessageBody { std::any::type_name::() ); } + + /// Converts this body into `BoxBody`. + #[inline] + fn boxed(self) -> BoxBody + where + Self: Sized + 'static, + { + BoxBody::new(self) + } } mod foreign_impls { @@ -656,7 +665,7 @@ mod tests { assert_eq!(body.take_complete_body(), b"test".as_ref()); // subsequent poll_next returns None - let waker = futures_util::task::noop_waker(); + let waker = futures_task::noop_waker(); let mut cx = Context::from_waker(&waker); assert!(Pin::new(&mut body).poll_next(&mut cx).map_err(drop) == Poll::Ready(None)); } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 9f799f669..aee9e80b4 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -194,7 +194,7 @@ impl Response { where B: MessageBody + 'static, { - self.map_body(|_, body| BoxBody::new(body)) + self.map_body(|_, body| body.boxed()) } /// Returns body, consuming this response. diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs index cb9038ff3..2ffeb5074 100644 --- a/awc/src/any_body.rs +++ b/awc/src/any_body.rs @@ -45,9 +45,7 @@ impl AnyBody { where B: MessageBody + 'static, { - Self::Body { - body: BoxBody::new(body), - } + Self::Body { body: body.boxed() } } /// Constructs new `AnyBody` instance from a slice of bytes by copying it. diff --git a/src/response/response.rs b/src/response/response.rs index 1900dd845..4fb4b44b6 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -244,8 +244,7 @@ impl HttpResponse { where B: MessageBody + 'static, { - // TODO: avoid double boxing with down-casting, if it improves perf - self.map_body(|_, body| BoxBody::new(body)) + self.map_body(|_, body| body.boxed()) } /// Extract response body diff --git a/src/service.rs b/src/service.rs index 36b3858e6..9ccf5274d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -451,7 +451,7 @@ impl ServiceResponse { where B: MessageBody + 'static, { - self.map_body(|_, body| BoxBody::new(body)) + self.map_body(|_, body| body.boxed()) } } From a2467718ac14d4ecf294ab1b2a398c47e97d47fa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 01:27:27 +0000 Subject: [PATCH 182/861] passthrough StreamLog error type --- src/middleware/logger.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 74daa26d5..d7fdb234f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -322,13 +322,10 @@ pin_project! { } } -impl MessageBody for StreamLog -where - B: MessageBody, - B::Error: Into, -{ - type Error = Error; +impl MessageBody for StreamLog { + type Error = B::Error; + #[inline] fn size(&self) -> BodySize { self.body.size() } @@ -344,7 +341,7 @@ where *this.size += chunk.len(); Poll::Ready(Some(Ok(chunk))) } - Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + Some(Err(err)) => Poll::Ready(Some(Err(err))), None => Poll::Ready(None), } } From 5359fa56c277362ae689f5170d36b74228291fb5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 01:29:41 +0000 Subject: [PATCH 183/861] include source for dispatch body errors --- actix-http/src/error.rs | 49 ++++++++++++++++----------------- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/service.rs | 6 ++-- actix-http/src/service.rs | 6 ++-- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index a04867ae1..3d2a918f4 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -332,31 +332,28 @@ impl From for Error { } /// A set of errors that can occur during dispatching HTTP requests. -#[derive(Debug, Display, Error, From)] -#[non_exhaustive] +#[derive(Debug, Display, From)] pub enum DispatchError { - /// Service error - // FIXME: display and error type + /// Service error. #[display(fmt = "Service Error")] - Service(#[error(not(source))] Response), + Service(Response), - /// Body error - // FIXME: display and error type - #[display(fmt = "Body Error")] - Body(#[error(not(source))] Box), + /// Body streaming error. + #[display(fmt = "Body error: {}", _0)] + Body(Box), - /// Upgrade service error + /// Upgrade service error. Upgrade, /// An `io::Error` that occurred while trying to read or write to a network stream. #[display(fmt = "IO error: {}", _0)] Io(io::Error), - /// Http request parse error. - #[display(fmt = "Parse error: {}", _0)] + /// Request parse error. + #[display(fmt = "Request parse error: {}", _0)] Parse(ParseError), - /// Http/2 error + /// HTTP/2 error. #[display(fmt = "{}", _0)] H2(h2::Error), @@ -368,21 +365,23 @@ pub enum DispatchError { #[display(fmt = "Connection shutdown timeout")] DisconnectTimeout, - /// Payload is not consumed - #[display(fmt = "Task is completed but request's payload is not consumed")] - PayloadIsNotConsumed, - - /// Malformed request - #[display(fmt = "Malformed request")] - MalformedRequest, - - /// Internal error + /// Internal error. #[display(fmt = "Internal error")] InternalError, +} - /// Unknown error - #[display(fmt = "Unknown error")] - Unknown, +impl StdError for DispatchError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + // TODO: error source extraction? + DispatchError::Service(_res) => None, + DispatchError::Body(err) => Some(&**err), + DispatchError::Io(err) => Some(err), + DispatchError::Parse(err) => Some(err), + DispatchError::H2(err) => Some(err), + _ => None, + } + } } /// A set of error that can occur during parsing content type. diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 64bf83e03..16d7c3c11 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -458,7 +458,7 @@ where } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Service(err.into())) + return Err(DispatchError::Body(err.into())) } Poll::Pending => return Ok(PollResponse::DoNothing), diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index c4e6e7714..43b7919a7 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -356,9 +356,9 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self._poll_ready(cx).map_err(|e| { - log::error!("HTTP/1 service readiness error: {:?}", e); - DispatchError::Service(e) + self._poll_ready(cx).map_err(|err| { + log::error!("HTTP/1 service readiness error: {:?}", err); + DispatchError::Service(err) }) } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 93168749d..cd2efe678 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -493,9 +493,9 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self._poll_ready(cx).map_err(|e| { - log::error!("HTTP service readiness error: {:?}", e); - DispatchError::Service(e) + self._poll_ready(cx).map_err(|err| { + log::error!("HTTP service readiness error: {:?}", err); + DispatchError::Service(err) }) } From 2cf27863cb99a450d35a460c73a70919dfd29470 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 14:13:54 +0000 Subject: [PATCH 184/861] remove direct dep on pin-project in -http (#2524) --- actix-http/Cargo.toml | 3 +- actix-http/src/h1/dispatcher.rs | 278 ++++++++++++++++++-------------- 2 files changed, 158 insertions(+), 123 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2e8ec1dfc..515574ab1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -45,7 +45,7 @@ __compress = [] actix-service = "2.0.0" actix-codec = "0.4.1" actix-utils = "3.0.0" -actix-rt = "2.2" +actix-rt = { version = "2.2", default-features = false } ahash = "0.7" base64 = "0.13" @@ -66,7 +66,6 @@ local-channel = "0.1" log = "0.4" mime = "0.3" percent-encoding = "2.1" -pin-project = "1.0.0" pin-project-lite = "0.2" rand = "0.8" sha-1 = "0.9" diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 16d7c3c11..472845e65 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,7 +15,7 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use log::{error, trace}; -use pin_project::pin_project; +use pin_project_lite::pin_project; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -46,79 +46,111 @@ bitflags! { } } -#[pin_project] -/// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher -where - S: Service, - S::Error: Into>, +// there's 2 versions of Dispatcher state because of: +// https://github.com/taiki-e/pin-project-lite/issues/3 +// +// tl;dr: pin-project-lite doesn't play well with other attribute macros - B: MessageBody, +#[cfg(not(test))] +pin_project! { + /// Dispatcher for HTTP/1.1 protocol + pub struct Dispatcher + where + S: Service, + S::Error: Into>, - X: Service, - X::Error: Into>, + B: MessageBody, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - #[pin] - inner: DispatcherState, + X: Service, + X::Error: Into>, - #[cfg(test)] - poll_count: u64, + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + #[pin] + inner: DispatcherState, + } } -#[pin_project(project = DispatcherStateProj)] -enum DispatcherState -where - S: Service, - S::Error: Into>, +#[cfg(test)] +pin_project! { + /// Dispatcher for HTTP/1.1 protocol + pub struct Dispatcher + where + S: Service, + S::Error: Into>, - B: MessageBody, + B: MessageBody, - X: Service, - X::Error: Into>, + X: Service, + X::Error: Into>, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - Normal(#[pin] InnerDispatcher), - Upgrade(#[pin] U::Future), + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + #[pin] + inner: DispatcherState, + + // used in tests + poll_count: u64, + } } -#[pin_project(project = InnerDispatcherProj)] -struct InnerDispatcher -where - S: Service, - S::Error: Into>, +pin_project! { + #[project = DispatcherStateProj] + enum DispatcherState + where + S: Service, + S::Error: Into>, - B: MessageBody, + B: MessageBody, - X: Service, - X::Error: Into>, + X: Service, + X::Error: Into>, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - flow: Rc>, - flags: Flags, - peer_addr: Option, - conn_data: Option>, - error: Option, + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + Normal { #[pin] inner: InnerDispatcher }, + Upgrade { #[pin] fut: U::Future }, + } +} - #[pin] - state: State, - payload: Option, - messages: VecDeque, +pin_project! { + #[project = InnerDispatcherProj] + struct InnerDispatcher + where + S: Service, + S::Error: Into>, - ka_expire: Instant, - #[pin] - ka_timer: Option, + B: MessageBody, - io: Option, - read_buf: BytesMut, - write_buf: BytesMut, - codec: Codec, + X: Service, + X::Error: Into>, + + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + flow: Rc>, + flags: Flags, + peer_addr: Option, + conn_data: Option>, + error: Option, + + #[pin] + state: State, + payload: Option, + messages: VecDeque, + + ka_expire: Instant, + #[pin] + ka_timer: Option, + + io: Option, + read_buf: BytesMut, + write_buf: BytesMut, + codec: Codec, + } } enum DispatcherMessage { @@ -127,19 +159,21 @@ enum DispatcherMessage { Error(Response<()>), } -#[pin_project(project = StateProj)] -enum State -where - S: Service, - X: Service, +pin_project! { + #[project = StateProj] + enum State + where + S: Service, + X: Service, - B: MessageBody, -{ - None, - ExpectCall(#[pin] X::Future), - ServiceCall(#[pin] S::Future), - SendPayload(#[pin] B), - SendErrorPayload(#[pin] BoxBody), + B: MessageBody, + { + None, + ExpectCall { #[pin] fut: X::Future }, + ServiceCall { #[pin] fut: S::Future }, + SendPayload { #[pin] body: B }, + SendErrorPayload { #[pin] body: BoxBody }, + } } impl State @@ -198,25 +232,27 @@ where }; Dispatcher { - inner: DispatcherState::Normal(InnerDispatcher { - flow, - flags, - peer_addr, - conn_data: conn_data.0.map(Rc::new), - error: None, + inner: DispatcherState::Normal { + inner: InnerDispatcher { + flow, + flags, + peer_addr, + conn_data: conn_data.0.map(Rc::new), + error: None, - state: State::None, - payload: None, - messages: VecDeque::new(), + state: State::None, + payload: None, + messages: VecDeque::new(), - ka_expire, - ka_timer, + ka_expire, + ka_timer, - io: Some(io), - read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - codec: Codec::new(config), - }), + io: Some(io), + read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), + codec: Codec::new(config), + }, + }, #[cfg(test)] poll_count: 0, @@ -316,7 +352,7 @@ where let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { BodySize::None | BodySize::Sized(0) => State::None, - _ => State::SendPayload(body), + _ => State::SendPayload { body }, }; self.project().state.set(state); Ok(()) @@ -330,7 +366,7 @@ where let size = self.as_mut().send_response_inner(message, &body)?; let state = match size { BodySize::None | BodySize::Sized(0) => State::None, - _ => State::SendErrorPayload(body), + _ => State::SendErrorPayload { body }, }; self.project().state.set(state); Ok(()) @@ -356,12 +392,12 @@ where // 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)); + let fut = this.flow.expect.call(req); + this.state.set(State::ExpectCall { fut }); } else { // the same as expect call. - let task = this.flow.service.call(req); - this.state.set(State::ServiceCall(task)); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); }; } @@ -381,7 +417,7 @@ where // all messages are dealt with. None => return Ok(PollResponse::DoNothing), }, - StateProj::ServiceCall(fut) => match fut.poll(cx) { + StateProj::ServiceCall { fut } => match fut.poll(cx) { // service call resolved. send response. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); @@ -407,11 +443,11 @@ where } }, - StateProj::SendPayload(mut stream) => { + StateProj::SendPayload { mut body } => { // keep populate writer buffer until buffer size limit hit, // get blocked or finished. while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { - match stream.as_mut().poll_next(cx) { + match body.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { this.codec .encode(Message::Chunk(Some(item)), this.write_buf)?; @@ -437,13 +473,13 @@ where return Ok(PollResponse::DrainWriteBuf); } - StateProj::SendErrorPayload(mut stream) => { + StateProj::SendErrorPayload { mut body } => { // TODO: de-dupe impl with SendPayload // keep populate writer buffer until buffer size limit hit, // get blocked or finished. while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { - match stream.as_mut().poll_next(cx) { + match body.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { this.codec .encode(Message::Chunk(Some(item)), this.write_buf)?; @@ -469,14 +505,14 @@ where return Ok(PollResponse::DrainWriteBuf); } - StateProj::ExpectCall(fut) => match fut.poll(cx) { + 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)); + this.state.set(State::ServiceCall { fut }); } // send expect error as response @@ -502,25 +538,25 @@ where let mut this = self.as_mut().project(); if req.head().expect() { // set dispatcher state so the future is pinned. - let task = this.flow.expect.call(req); - this.state.set(State::ExpectCall(task)); + let fut = this.flow.expect.call(req); + this.state.set(State::ExpectCall { fut }); } else { // the same as above. - let task = this.flow.service.call(req); - this.state.set(State::ServiceCall(task)); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); }; // eagerly poll the future for once(or twice if expect is resolved immediately). loop { match self.as_mut().project().state.project() { - StateProj::ExpectCall(fut) => { + 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.service.call(req); - this.state.set(State::ServiceCall(task)); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); continue; } // future is pending. return Ok(()) to notify that a new state is @@ -536,7 +572,7 @@ where } } } - StateProj::ServiceCall(fut) => { + 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 @@ -901,7 +937,7 @@ where } match this.inner.project() { - DispatcherStateProj::Normal(mut inner) => { + DispatcherStateProj::Normal { mut inner } => { inner.as_mut().poll_keepalive(cx)?; if inner.flags.contains(Flags::SHUTDOWN) { @@ -941,7 +977,7 @@ where self.as_mut() .project() .inner - .set(DispatcherState::Upgrade(upgrade)); + .set(DispatcherState::Upgrade { fut: upgrade }); return self.poll(cx); } }; @@ -993,8 +1029,8 @@ where } } } - DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| { - error!("Upgrade handler error: {}", e); + DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { + error!("Upgrade handler error: {}", err); DispatchError::Upgrade }), } @@ -1088,7 +1124,7 @@ mod tests { Poll::Ready(res) => assert!(res.is_err()), } - if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert_eq!( &inner.project().io.take().unwrap().write_buf[..26], @@ -1123,7 +1159,7 @@ mod tests { actix_rt::pin!(h1); - assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), @@ -1133,7 +1169,7 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 2); - if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); @@ -1177,7 +1213,7 @@ mod tests { actix_rt::pin!(h1); - assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), @@ -1187,7 +1223,7 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 1); - if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); @@ -1237,13 +1273,13 @@ mod tests { actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_pending()); - assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); // polls: manual assert_eq!(h1.poll_count, 1); eprintln!("poll count: {}", h1.poll_count); - if let DispatcherState::Normal(ref inner) = h1.inner { + if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); let res = &io.write_buf()[..]; assert_eq!( @@ -1258,7 +1294,7 @@ mod tests { // polls: manual manual shutdown assert_eq!(h1.poll_count, 3); - if let DispatcherState::Normal(ref inner) = h1.inner { + if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); let mut res = (&io.write_buf()[..]).to_owned(); stabilize_date_header(&mut res); @@ -1309,12 +1345,12 @@ mod tests { actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); // polls: manual shutdown assert_eq!(h1.poll_count, 2); - if let DispatcherState::Normal(ref inner) = h1.inner { + if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); let mut res = (&io.write_buf()[..]).to_owned(); stabilize_date_header(&mut res); @@ -1386,7 +1422,7 @@ mod tests { actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); + assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); // polls: manual shutdown assert_eq!(h1.poll_count, 2); From 57ea322ce5acefba4f17cb090d9dc5b7da4c1de1 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 17 Dec 2021 22:09:08 +0300 Subject: [PATCH 185/861] simplify MessageBody::complete_body interface (#2522) --- actix-http/src/body/boxed.rs | 57 ++++-- actix-http/src/body/either.rs | 18 +- actix-http/src/body/message_body.rs | 289 +++++++--------------------- actix-http/src/body/none.rs | 9 +- actix-http/src/encoding/encoder.rs | 54 +++--- actix-http/src/h1/dispatcher.rs | 6 +- src/dev.rs | 8 + 7 files changed, 149 insertions(+), 292 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index 7581bec88..a2d7540c4 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -8,10 +8,16 @@ use std::{ use bytes::Bytes; use super::{BodySize, MessageBody, MessageBodyMapErr}; -use crate::Error; +use crate::body; /// A boxed message body with boxed errors. -pub struct BoxBody(Pin>>>); +pub struct BoxBody(BoxBodyInner); + +enum BoxBodyInner { + None(body::None), + Bytes(Bytes), + Stream(Pin>>>), +} impl BoxBody { /// Same as `MessageBody::boxed`. @@ -23,29 +29,42 @@ impl BoxBody { where B: MessageBody + 'static, { - let body = MessageBodyMapErr::new(body, Into::into); - Self(Box::pin(body)) + match body.size() { + BodySize::None => Self(BoxBodyInner::None(body::None)), + _ => match body.try_into_bytes() { + Ok(bytes) => Self(BoxBodyInner::Bytes(bytes)), + Err(body) => { + let body = MessageBodyMapErr::new(body, Into::into); + Self(BoxBodyInner::Stream(Box::pin(body))) + } + }, + } } /// Returns a mutable pinned reference to the inner message body type. #[inline] - pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody>)> { - self.0.as_mut() + pub fn as_pin_mut(&mut self) -> Pin<&mut Self> { + Pin::new(self) } } impl fmt::Debug for BoxBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO show BoxBodyInner f.write_str("BoxBody(dyn MessageBody)") } } impl MessageBody for BoxBody { - type Error = Error; + type Error = Box; #[inline] fn size(&self) -> BodySize { - self.0.size() + match &self.0 { + BoxBodyInner::None(none) => none.size(), + BoxBodyInner::Bytes(bytes) => bytes.size(), + BoxBodyInner::Stream(stream) => stream.size(), + } } #[inline] @@ -53,20 +72,20 @@ impl MessageBody for BoxBody { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - self.0 - .as_mut() - .poll_next(cx) - .map_err(|err| Error::new_body().with_cause(err)) + match &mut self.0 { + BoxBodyInner::None(_) => Poll::Ready(None), + BoxBodyInner::Bytes(bytes) => Pin::new(bytes).poll_next(cx).map_err(Into::into), + BoxBodyInner::Stream(stream) => Pin::new(stream).poll_next(cx), + } } #[inline] - fn is_complete_body(&self) -> bool { - self.0.is_complete_body() - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - self.0.take_complete_body() + fn try_into_bytes(self) -> Result { + match self.0 { + BoxBodyInner::None(none) => Ok(none.try_into_bytes().unwrap()), + BoxBodyInner::Bytes(bytes) => Ok(bytes.try_into_bytes().unwrap()), + _ => Err(self), + } } #[inline] diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index 3a4082dc9..add1eab7c 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -74,18 +74,14 @@ where } #[inline] - fn is_complete_body(&self) -> bool { + fn try_into_bytes(self) -> Result { match self { - EitherBody::Left { body } => body.is_complete_body(), - EitherBody::Right { body } => body.is_complete_body(), - } - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - match self { - EitherBody::Left { body } => body.take_complete_body(), - EitherBody::Right { body } => body.take_complete_body(), + EitherBody::Left { body } => body + .try_into_bytes() + .map_err(|body| EitherBody::Left { body }), + EitherBody::Right { body } => body + .try_into_bytes() + .map_err(|body| EitherBody::Right { body }), } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 075ae7220..bd13e75ec 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -31,51 +31,14 @@ pub trait MessageBody { cx: &mut Context<'_>, ) -> Poll>>; - /// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`. + /// Convert this body into `Bytes`. /// - /// This method's implementation should agree with [`take_complete_body`] and should always be - /// checked before taking the body. - /// - /// The default implementation returns `false. - /// - /// [`take_complete_body`]: MessageBody::take_complete_body - fn is_complete_body(&self) -> bool { - false - } - - /// Returns the complete chunk of body bytes. - /// - /// Implementors of this method should note the following: - /// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of - /// performing this check is delegated to the caller. - /// - If the result of [`is_complete_body`] is conditional, that condition should be given - /// equivalent attention here. - /// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic. - /// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless - /// the chunk is guaranteed to be empty. - /// - /// The default implementation panics unconditionally, indicating a control flow bug in the - /// calling code. - /// - /// # Panics - /// With a correct implementation, panics if called without first checking [`is_complete_body`]. - /// - /// [`is_complete_body`]: MessageBody::is_complete_body - /// [`take_complete_body`]: MessageBody::take_complete_body - /// [`poll_next`]: MessageBody::poll_next - fn take_complete_body(&mut self) -> Bytes { - assert!( - self.is_complete_body(), - "type ({}) allows taking complete body but did not provide an implementation \ - of `take_complete_body`", - std::any::type_name::() - ); - - unimplemented!( - "type ({}) does not allow taking complete body; caller should make sure to \ - check `is_complete_body` first", - std::any::type_name::() - ); + /// Bodies with `BodySize::None` are allowed to return empty `Bytes`. + fn try_into_bytes(self) -> Result + where + Self: Sized, + { + Err(self) } /// Converts this body into `BoxBody`. @@ -104,14 +67,6 @@ mod foreign_impls { ) -> Poll>> { match *self {} } - - fn is_complete_body(&self) -> bool { - true - } - - fn take_complete_body(&mut self) -> Bytes { - match *self {} - } } impl MessageBody for () { @@ -131,13 +86,8 @@ mod foreign_impls { } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::new() + fn try_into_bytes(self) -> Result { + Ok(Bytes::new()) } } @@ -159,16 +109,6 @@ mod foreign_impls { ) -> Poll>> { Pin::new(self.get_mut().as_mut()).poll_next(cx) } - - #[inline] - fn is_complete_body(&self) -> bool { - self.as_ref().is_complete_body() - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - self.as_mut().take_complete_body() - } } impl MessageBody for Pin> @@ -189,38 +129,6 @@ mod foreign_impls { ) -> Poll>> { self.get_mut().as_mut().poll_next(cx) } - - #[inline] - fn is_complete_body(&self) -> bool { - self.as_ref().is_complete_body() - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - debug_assert!( - self.is_complete_body(), - "inner type \"{}\" does not allow taking complete body; caller should make sure to \ - call `is_complete_body` first", - std::any::type_name::(), - ); - - // we do not have DerefMut access to call take_complete_body directly but since - // is_complete_body is true we should expect the entire bytes chunk in one poll_next - - let waker = futures_task::noop_waker(); - let mut cx = Context::from_waker(&waker); - - match self.as_mut().poll_next(&mut cx) { - Poll::Ready(Some(Ok(data))) => data, - _ => { - panic!( - "inner type \"{}\" indicated it allows taking complete body but failed to \ - return Bytes when polled", - std::any::type_name::() - ); - } - } - } } impl MessageBody for &'static [u8] { @@ -232,24 +140,19 @@ mod foreign_impls { } fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - Poll::Ready(Some(Ok(self.take_complete_body()))) + Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut()))))) } } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::from_static(mem::take(self)) + fn try_into_bytes(self) -> Result { + Ok(Bytes::from_static(self)) } } @@ -262,24 +165,19 @@ mod foreign_impls { } fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - Poll::Ready(Some(Ok(self.take_complete_body()))) + Poll::Ready(Some(Ok(mem::take(self.get_mut())))) } } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - mem::take(self) + fn try_into_bytes(self) -> Result { + Ok(self) } } @@ -292,24 +190,19 @@ mod foreign_impls { } fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - Poll::Ready(Some(Ok(self.take_complete_body()))) + Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze()))) } } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - mem::take(self).freeze() + fn try_into_bytes(self) -> Result { + Ok(self.freeze()) } } @@ -322,24 +215,19 @@ mod foreign_impls { } fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { if self.is_empty() { Poll::Ready(None) } else { - Poll::Ready(Some(Ok(self.take_complete_body()))) + Poll::Ready(Some(Ok(mem::take(self.get_mut()).into()))) } } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::from(mem::take(self)) + fn try_into_bytes(self) -> Result { + Ok(Bytes::from(self)) } } @@ -365,13 +253,8 @@ mod foreign_impls { } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::from_static(mem::take(self).as_bytes()) + fn try_into_bytes(self) -> Result { + Ok(Bytes::from_static(self.as_bytes())) } } @@ -396,13 +279,8 @@ mod foreign_impls { } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::from(mem::take(self)) + fn try_into_bytes(self) -> Result { + Ok(Bytes::from(self)) } } @@ -423,13 +301,8 @@ mod foreign_impls { } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - mem::take(self).into_bytes() + fn try_into_bytes(self) -> Result { + Ok(self.into_bytes()) } } } @@ -486,13 +359,9 @@ where } #[inline] - fn is_complete_body(&self) -> bool { - self.body.is_complete_body() - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - self.body.take_complete_body() + fn try_into_bytes(self) -> Result { + let Self { body, mapper } = self; + body.try_into_bytes().map_err(|body| Self { body, mapper }) } } @@ -503,6 +372,7 @@ mod tests { use bytes::{Bytes, BytesMut}; use super::*; + use crate::body::{self, EitherBody}; macro_rules! assert_poll_next { ($pin:expr, $exp:expr) => { @@ -604,70 +474,45 @@ mod tests { assert_poll_next!(pl, Bytes::from("test")); } - #[test] - fn take_string() { - let mut data = "test".repeat(2); - let data_bytes = Bytes::from(data.clone()); - assert!(data.is_complete_body()); - assert_eq!(data.take_complete_body(), data_bytes); - - let mut big_data = "test".repeat(64 * 1024); - let data_bytes = Bytes::from(big_data.clone()); - assert!(big_data.is_complete_body()); - assert_eq!(big_data.take_complete_body(), data_bytes); - } - - #[test] - fn take_boxed_equivalence() { - let mut data = Bytes::from_static(b"test"); - assert!(data.is_complete_body()); - assert_eq!(data.take_complete_body(), b"test".as_ref()); - - let mut data = Box::new(Bytes::from_static(b"test")); - assert!(data.is_complete_body()); - assert_eq!(data.take_complete_body(), b"test".as_ref()); - - let mut data = Box::pin(Bytes::from_static(b"test")); - assert!(data.is_complete_body()); - assert_eq!(data.take_complete_body(), b"test".as_ref()); - } - - #[test] - fn take_policy() { - let mut data = Bytes::from_static(b"test"); - // first call returns chunk - assert_eq!(data.take_complete_body(), b"test".as_ref()); - // second call returns empty - assert_eq!(data.take_complete_body(), b"".as_ref()); - - let waker = futures_task::noop_waker(); - let mut cx = Context::from_waker(&waker); - let mut data = Bytes::from_static(b"test"); - // take returns whole chunk - assert_eq!(data.take_complete_body(), b"test".as_ref()); - // subsequent poll_next returns None - assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None)); - } - - #[test] - fn complete_body_combinators() { - use crate::body::{BoxBody, EitherBody}; - + #[actix_rt::test] + async fn complete_body_combinators() { + let body = Bytes::from_static(b"test"); + let body = BoxBody::new(body); + let body = EitherBody::<_, ()>::left(body); + let body = EitherBody::<(), _>::right(body); + // Do not support try_into_bytes: + // let body = Box::new(body); + // let body = Box::pin(body); + + assert_eq!(body.try_into_bytes().unwrap(), Bytes::from("test")); + } + + #[actix_rt::test] + async fn complete_body_combinators_poll() { let body = Bytes::from_static(b"test"); let body = BoxBody::new(body); let body = EitherBody::<_, ()>::left(body); let body = EitherBody::<(), _>::right(body); - let body = Box::new(body); - let body = Box::pin(body); let mut body = body; - assert!(body.is_complete_body()); - assert_eq!(body.take_complete_body(), b"test".as_ref()); + assert_eq!(body.size(), BodySize::Sized(4)); + assert_poll_next!(Pin::new(&mut body), Bytes::from("test")); + assert_poll_next_none!(Pin::new(&mut body)); + } - // subsequent poll_next returns None - let waker = futures_task::noop_waker(); - let mut cx = Context::from_waker(&waker); - assert!(Pin::new(&mut body).poll_next(&mut cx).map_err(drop) == Poll::Ready(None)); + #[actix_rt::test] + async fn none_body_combinators() { + fn none_body() -> BoxBody { + let body = body::None; + let body = BoxBody::new(body); + let body = EitherBody::<_, ()>::left(body); + let body = EitherBody::<(), _>::right(body); + body.boxed() + } + + assert_eq!(none_body().size(), BodySize::None); + assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new()); + assert_poll_next_none!(Pin::new(&mut none_body())); } // down-casting used to be done with a method on MessageBody trait diff --git a/actix-http/src/body/none.rs b/actix-http/src/body/none.rs index bb494078f..0e7bbe5a9 100644 --- a/actix-http/src/body/none.rs +++ b/actix-http/src/body/none.rs @@ -42,12 +42,7 @@ impl MessageBody for None { } #[inline] - fn is_complete_body(&self) -> bool { - true - } - - #[inline] - fn take_complete_body(&mut self) -> Bytes { - Bytes::new() + fn try_into_bytes(self) -> Result { + Ok(Bytes::new()) } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index fa294ab0d..70448a115 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -53,7 +53,7 @@ impl Encoder { } } - pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self { + pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT @@ -65,11 +65,9 @@ impl Encoder { return Self::none(); } - let body = if body.is_complete_body() { - let body = body.take_complete_body(); - EncoderBody::Full { body } - } else { - EncoderBody::Stream { body } + let body = match body.try_into_bytes() { + Ok(body) => EncoderBody::Full { body }, + Err(body) => EncoderBody::Stream { body }, }; if can_encode { @@ -133,21 +131,14 @@ where } } - fn is_complete_body(&self) -> bool { + fn try_into_bytes(self) -> Result + where + Self: Sized, + { match self { - EncoderBody::None => true, - EncoderBody::Full { .. } => true, - EncoderBody::Stream { .. } => false, - } - } - - fn take_complete_body(&mut self) -> Bytes { - match self { - EncoderBody::None => Bytes::new(), - EncoderBody::Full { body } => body.take_complete_body(), - EncoderBody::Stream { .. } => { - panic!("EncoderBody::Stream variant cannot be taken") - } + EncoderBody::None => Ok(Bytes::new()), + EncoderBody::Full { body } => Ok(body), + _ => Err(self), } } } @@ -234,19 +225,20 @@ where } } - fn is_complete_body(&self) -> bool { + fn try_into_bytes(mut self) -> Result + where + Self: Sized, + { if self.encoder.is_some() { - false + Err(self) } else { - self.body.is_complete_body() - } - } - - fn take_complete_body(&mut self) -> Bytes { - if self.encoder.is_some() { - panic!("compressed body stream cannot be taken") - } else { - self.body.take_complete_body() + match self.body.try_into_bytes() { + Ok(body) => Ok(body), + Err(body) => { + self.body = body; + Err(self) + } + } } } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 472845e65..5c0cb64af 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -22,7 +22,7 @@ use crate::{ config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - Extensions, OnConnectData, Request, Response, StatusCode, + Error, Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -494,7 +494,9 @@ where } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Body(err.into())) + return Err(DispatchError::Body( + Error::new_body().with_cause(err).into(), + )) } Poll::Pending => return Ok(PollResponse::DoNothing), diff --git a/src/dev.rs b/src/dev.rs index d4a64985c..edcc158f8 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -139,4 +139,12 @@ impl crate::body::MessageBody for AnyBody { AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx), } } + + fn try_into_bytes(self) -> Result { + match self { + AnyBody::None => Ok(crate::web::Bytes::new()), + AnyBody::Full { body } => Ok(body), + _ => Err(self), + } + } } From aa31086af5ce0c67eeeec3fc8858cc35770d8a40 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 19:16:42 +0000 Subject: [PATCH 186/861] improve BoxBody Debug impl --- actix-http/src/body/boxed.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index a2d7540c4..0c6950a1f 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -11,6 +11,7 @@ use super::{BodySize, MessageBody, MessageBodyMapErr}; use crate::body; /// A boxed message body with boxed errors. +#[derive(Debug)] pub struct BoxBody(BoxBodyInner); enum BoxBodyInner { @@ -19,6 +20,16 @@ enum BoxBodyInner { Stream(Pin>>>), } +impl fmt::Debug for BoxBodyInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None(arg0) => f.debug_tuple("None").field(arg0).finish(), + Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(), + Self::Stream(_) => f.debug_tuple("Stream").field(&"dyn MessageBody").finish(), + } + } +} + impl BoxBody { /// Same as `MessageBody::boxed`. /// @@ -48,13 +59,6 @@ impl BoxBody { } } -impl fmt::Debug for BoxBody { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO show BoxBodyInner - f.write_str("BoxBody(dyn MessageBody)") - } -} - impl MessageBody for BoxBody { type Error = Box; From 1d6f5ba6d696515d8b8eb71f0e353b2fc9a797b8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 19:19:21 +0000 Subject: [PATCH 187/861] improve codegen on BoxBody poll_next --- actix-http/src/body/boxed.rs | 14 +++++++++----- actix-http/src/body/message_body.rs | 10 +++++++++- actix-http/src/encoding/encoder.rs | 4 ++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index 0c6950a1f..d109a6a74 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -77,17 +77,21 @@ impl MessageBody for BoxBody { cx: &mut Context<'_>, ) -> Poll>> { match &mut self.0 { - BoxBodyInner::None(_) => Poll::Ready(None), - BoxBodyInner::Bytes(bytes) => Pin::new(bytes).poll_next(cx).map_err(Into::into), - BoxBodyInner::Stream(stream) => Pin::new(stream).poll_next(cx), + BoxBodyInner::None(body) => { + Pin::new(body).poll_next(cx).map_err(|err| match err {}) + } + BoxBodyInner::Bytes(body) => { + Pin::new(body).poll_next(cx).map_err(|err| match err {}) + } + BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx), } } #[inline] fn try_into_bytes(self) -> Result { match self.0 { - BoxBodyInner::None(none) => Ok(none.try_into_bytes().unwrap()), - BoxBodyInner::Bytes(bytes) => Ok(bytes.try_into_bytes().unwrap()), + BoxBodyInner::None(body) => Ok(body.try_into_bytes().unwrap()), + BoxBodyInner::Bytes(body) => Ok(body.try_into_bytes().unwrap()), _ => Err(self), } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index bd13e75ec..cf65bac2d 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -33,7 +33,8 @@ pub trait MessageBody { /// Convert this body into `Bytes`. /// - /// Bodies with `BodySize::None` are allowed to return empty `Bytes`. + /// Body types with `BodySize::None` are allowed to return empty `Bytes`. + #[inline] fn try_into_bytes(self) -> Result where Self: Sized, @@ -139,6 +140,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -164,6 +166,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -189,6 +192,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -214,6 +218,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -239,6 +244,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -266,6 +272,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -292,6 +299,7 @@ mod foreign_impls { BodySize::Sized(self.len() as u64) } + #[inline] fn poll_next( self: Pin<&mut Self>, _cx: &mut Context<'_>, diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 70448a115..d06d6b4d8 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -108,6 +108,7 @@ where { type Error = EncoderError; + #[inline] fn size(&self) -> BodySize { match self { EncoderBody::None => BodySize::None, @@ -131,6 +132,7 @@ where } } + #[inline] fn try_into_bytes(self) -> Result where Self: Sized, @@ -149,6 +151,7 @@ where { type Error = EncoderError; + #[inline] fn size(&self) -> BodySize { if self.encoder.is_some() { BodySize::Stream @@ -225,6 +228,7 @@ where } } + #[inline] fn try_into_bytes(mut self) -> Result where Self: Sized, From 5842a3279daff2a34bdf1151d317ebbe813288c7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 19:35:08 +0000 Subject: [PATCH 188/861] update messagebody documentation --- actix-http/CHANGES.md | 9 ++++++++- actix-http/src/body/message_body.rs | 20 ++++++++++++++++---- actix-http/src/response.rs | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 598ef9c0e..806e32cc0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,12 +1,19 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] + ### Changed * Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] * Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] * Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +### Removed +* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] + [#2510]: https://github.com/actix/actix-web/pull/2510 +[#2522]: https://github.com/actix/actix-web/pull/2522 ## 3.0.0-beta.15 - 2021-12-11 @@ -27,7 +34,7 @@ * `Request::take_conn_data()`. [#2491] * `Request::take_req_data()`. [#2487] * `impl Clone` for `RequestHead`. [#2487] -* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497] +* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] * New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index cf65bac2d..0a605a69a 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -17,11 +17,15 @@ use super::{BodySize, BoxBody}; /// An interface types that can converted to bytes and used as response bodies. // TODO: examples pub trait MessageBody { - // TODO: consider this bound to only fmt::Display since the error type is not really used - // and there is an impl for Into> on String + /// The type of error that will be returned if streaming body fails. + /// + /// Since it is not appropriate to generate a response mid-stream, it only requires `Error` for + /// internal use and logging. type Error: Into>; /// Body size hint. + /// + /// If [`BodySize::None`] is returned, optimizations that skip reading the body are allowed. fn size(&self) -> BodySize; /// Attempt to pull out the next chunk of body bytes. @@ -31,9 +35,17 @@ pub trait MessageBody { cx: &mut Context<'_>, ) -> Poll>>; - /// Convert this body into `Bytes`. + /// Try to convert into the complete chunk of body bytes. /// - /// Body types with `BodySize::None` are allowed to return empty `Bytes`. + /// Implement this method if the entire body can be trivially extracted. This is useful for + /// optimizations where `poll_next` calls can be avoided. + /// + /// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling + /// this method, it is recommended to check `size` first and return early. + /// + /// # Errors + /// The default implementation will error and return the original type back to the caller for + /// further use. #[inline] fn try_into_bytes(self) -> Result where diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index aee9e80b4..a0e6d9b7c 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -170,7 +170,7 @@ impl Response { /// Returns split head and body. /// /// # Implementation Notes - /// Due to internal performance optimisations, the first element of the returned tuple is a + /// Due to internal performance optimizations, the first element of the returned tuple is a /// `Response` as well but only contains the head of the response this was called on. pub fn into_parts(self) -> (Response<()>, B) { self.replace_body(()) From ae47d96fc6831d6f27a05fb05e47c464fe4f45e1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 20:56:54 +0000 Subject: [PATCH 189/861] use body::None in encoder body --- actix-http/src/encoding/encoder.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index d06d6b4d8..b565bb2b5 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -25,7 +25,7 @@ use zstd::stream::write::Encoder as ZstdEncoder; use super::Writer; use crate::{ - body::{BodySize, MessageBody}, + body::{self, BodySize, MessageBody}, error::BlockingError, header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, ResponseHead, StatusCode, @@ -46,7 +46,9 @@ pin_project! { impl Encoder { fn none() -> Self { Encoder { - body: EncoderBody::None, + body: EncoderBody::None { + body: body::None::new(), + }, encoder: None, fut: None, eof: true, @@ -96,7 +98,7 @@ impl Encoder { pin_project! { #[project = EncoderBodyProj] enum EncoderBody { - None, + None { body: body::None }, Full { body: Bytes }, Stream { #[pin] body: B }, } @@ -111,7 +113,7 @@ where #[inline] fn size(&self) -> BodySize { match self { - EncoderBody::None => BodySize::None, + EncoderBody::None { body } => body.size(), EncoderBody::Full { body } => body.size(), EncoderBody::Stream { body } => body.size(), } @@ -122,7 +124,9 @@ where cx: &mut Context<'_>, ) -> Poll>> { match self.project() { - EncoderBodyProj::None => Poll::Ready(None), + EncoderBodyProj::None { body } => { + Pin::new(body).poll_next(cx).map_err(|err| match err {}) + } EncoderBodyProj::Full { body } => { Pin::new(body).poll_next(cx).map_err(|err| match err {}) } @@ -138,8 +142,8 @@ where Self: Sized, { match self { - EncoderBody::None => Ok(Bytes::new()), - EncoderBody::Full { body } => Ok(body), + EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()), + EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()), _ => Err(self), } } From 7bf47967cc77090dad5dd247af55bd00c7808260 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 20:57:51 +0000 Subject: [PATCH 190/861] prepare actix-http release 3.0.0-beta.16 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e20529e1a..020fb03b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" actix-router = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.6" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index edb7cfab4..792f479d0 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-beta.14", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 449fa342e..841a9a241 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 806e32cc0..218ed5a6e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.16 - 2021-12-17 ### Added * New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 515574ab1..03ee1409e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.15" +version = "3.0.0-beta.16" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] diff --git a/actix-http/README.md b/actix-http/README.md index a2aa41333..731d7a48e 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.15)](https://docs.rs/actix-http/3.0.0-beta.15) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.16)](https://docs.rs/actix-http/3.0.0-beta.16) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.15) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.16) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6fd1211d9..8c3c86f08 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 71f99f791..67a0a087d 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" actix-http-test = "3.0.0-beta.9" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 128d68c15..e8145ee82 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" actix-web = { version = "4.0.0-beta.14", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 60a95871c..4b1e00dde 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.15" +actix-http = "3.0.0-beta.16" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.15", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.16", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } From 6c2c7b68e2d6eb5ee1c5cf879e8874928e420e72 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 20:59:01 +0000 Subject: [PATCH 191/861] prepare actix-web release 4.0.0-beta.15 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6494ba4f6..1c0691efe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.15 - 2021-12-17 ### Added * Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] * Implement `Debug` for `DefaultHeaders`. [#2510] diff --git a/Cargo.toml b/Cargo.toml index 020fb03b5..3c4ddd1b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.14" +version = "4.0.0-beta.15" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index 4a1671905..5cce9f3b9 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.14)](https://docs.rs/actix-web/4.0.0-beta.14) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.15)](https://docs.rs/actix-web/4.0.0-beta.15) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.14) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.15)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 792f479d0..3e7c377bf 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.16" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.14", default-features = false } +actix-web = { version = "4.0.0-beta.15", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,4 +44,4 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.8" -actix-web = "4.0.0-beta.14" +actix-web = "4.0.0-beta.15" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 841a9a241..70855b5e6 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.2", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.16" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 03ee1409e..9f93bf6d2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -83,7 +83,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } -actix-web = "4.0.0-beta.14" +actix-web = "4.0.0-beta.15" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 8c3c86f08..c7145e542 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.14", default-features = false } +actix-web = { version = "4.0.0-beta.15", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 67a0a087d..c2a7a3211 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.9" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e8145ee82..a2a69153d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.16" -actix-web = { version = "4.0.0-beta.14", default-features = false } +actix-web = { version = "4.0.0-beta.15", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 211f19da6..6571e2a24 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.8" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.14" +actix-web = "4.0.0-beta.15" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 4b1e00dde..9ca6acb75 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.1" actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.14", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.15", features = ["openssl"] } brotli2 = "0.3.2" env_logger = "0.9" From 8340b63b7b18009b006671ff9f1fbb790d67bf19 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 20:59:59 +0000 Subject: [PATCH 192/861] prepare awc release 3.0.0-beta.14 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c4ddd1b0..c46211821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.13", features = ["openssl"] } +awc = { version = "3.0.0-beta.14", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 70855b5e6..0c205fc2a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0-rc.1" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.1" -awc = { version = "3.0.0-beta.13", default-features = false } +awc = { version = "3.0.0-beta.14", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index c2a7a3211..9f409019b 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index a2a69153d..6c45a5479 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.8" -awc = { version = "3.0.0-beta.13", default-features = false } +awc = { version = "3.0.0-beta.14", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 8a3fea46a..ef0f9faaa 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.14 - 2021-12-17 * Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9ca6acb75..dc9e503b1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.13" +version = "3.0.0-beta.14" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index f3c5452fc..3fbdd903a 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.13)](https://docs.rs/awc/3.0.0-beta.13) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.14)](https://docs.rs/awc/3.0.0-beta.14) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.13/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.13) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.14/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.14) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 73bbe56971bff3cdcf8e6c0098351c4daf76b74e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 21:00:15 +0000 Subject: [PATCH 193/861] prepare actix-test release 0.1.0-beta.9 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c46211821..af6b5909a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.14", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3e7c377bf..c74a8e9a6 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.8" +actix-test = "0.1.0-beta.9" actix-web = "4.0.0-beta.15" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index b7107b44f..ef78ac54a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.9 - 2021-12-17 * Re-export `actix_http::body::to_bytes`. [#2518] * Update `actix_web::test` re-exports. [#2518] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 9f409019b..7957b3a9c 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.8" +version = "0.1.0-beta.9" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 6c45a5479..3f213f378 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.8" +actix-test = "0.1.0-beta.9" awc = { version = "3.0.0-beta.14", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 6571e2a24..502483599 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ actix-router = "0.5.0-beta.2" [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.8" +actix-test = "0.1.0-beta.9" actix-utils = "3.0.0" actix-web = "4.0.0-beta.15" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index dc9e503b1..4cab20d05 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-http = { version = "3.0.0-beta.16", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" -actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.15", features = ["openssl"] } From 9cd8526085b5aa4538d17495f8e21313d7c53527 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 21:23:00 +0000 Subject: [PATCH 194/861] prepare actix-router release 0.5.0-beta.3 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- scripts/unreleased | 41 ++++++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100755 scripts/unreleased diff --git a/Cargo.toml b/Cargo.toml index af6b5909a..02bef3af6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } actix-http = "3.0.0-beta.16" -actix-router = "0.5.0-beta.2" +actix-router = "0.5.0-beta.3" actix-web-codegen = "0.5.0-beta.6" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index c2858f2ba..d0ed55c88 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.3 - 2021-12-17 * Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index b95bca505..afd39dfd3 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-beta.2" +version = "0.5.0-beta.3" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 502483599..8d42137e7 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true quote = "1" syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" -actix-router = "0.5.0-beta.2" +actix-router = "0.5.0-beta.3" [dev-dependencies] actix-macros = "0.2.3" diff --git a/scripts/unreleased b/scripts/unreleased new file mode 100755 index 000000000..4dfa2d9ae --- /dev/null +++ b/scripts/unreleased @@ -0,0 +1,41 @@ +#!/bin/sh + +set -euo pipefail + +bold="\033[1m" +reset="\033[0m" + +unreleased_for() { + DIR=$1 + + CARGO_MANIFEST=$DIR/Cargo.toml + CHANGELOG_FILE=$DIR/CHANGES.md + + # get current version + PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" + CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")" + + CHANGE_CHUNK_FILE="$(mktemp)" + + # get changelog chunk and save to temp file + cat "$CHANGELOG_FILE" | + # skip up to unreleased heading + sed '1,/Unreleased/ d' | + # take up to previous version heading + sed "/$CURRENT_VERSION/ q" | + # drop last line + sed '$d' \ + >"$CHANGE_CHUNK_FILE" + + # if word count of changelog chunk is 0 then exit + if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then + return 0; + fi + + echo "${bold}# ${PACKAGE_NAME}${reset} since ${bold}v$CURRENT_VERSION${reset}" + cat "$CHANGE_CHUNK_FILE" +} + +for f in $(fd --absolute-path CHANGES.md); do + unreleased_for $(dirname $f) +done From 0bd5ccc43285ff871c1ea908a74e1b1d9e0f1409 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 17 Dec 2021 21:39:15 +0000 Subject: [PATCH 195/861] update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1c0691efe..857974d3f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,7 @@ * Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] * Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] * Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -* Relax body type and error bounds on test utilities. +* Relax body type and error bounds on test utilities. [#2518] ### Removed * Top-level `EitherExtractError` export. [#2510] From 84ea9e7e8849d98108a66990f8508fc014d78d19 Mon Sep 17 00:00:00 2001 From: Thales Date: Fri, 17 Dec 2021 21:05:12 -0300 Subject: [PATCH 196/861] http: Replace header::map::GetAll with std::slice::Iter (#2527) --- actix-http/CHANGES.md | 4 +++ actix-http/src/header/map.rs | 55 ++++-------------------------------- src/response/response.rs | 2 +- 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 218ed5a6e..c5e57e1a4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +* `header::map::GetAll` iterator, its `Iterator::size_hint` method was wrongly implemented. Replaced with `std::slice::Iter`. [#2527] + +[#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 748410375..478867ed0 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -306,8 +306,11 @@ impl HeaderMap { /// assert_eq!(set_cookies_iter.next().unwrap(), "two=2"); /// assert!(set_cookies_iter.next().is_none()); /// ``` - pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> { - GetAll::new(self.get_value(key)) + pub fn get_all(&self, key: impl AsHeaderName) -> std::slice::Iter<'_, HeaderValue> { + match self.get_value(key) { + Some(value) => value.iter(), + None => (&[]).iter(), + } } // TODO: get_all_mut ? @@ -602,52 +605,6 @@ impl<'a> IntoIterator for &'a HeaderMap { } } -/// Iterator over borrowed values with the same associated name. -/// -/// See [`HeaderMap::get_all`]. -#[derive(Debug)] -pub struct GetAll<'a> { - idx: usize, - value: Option<&'a Value>, -} - -impl<'a> GetAll<'a> { - fn new(value: Option<&'a Value>) -> Self { - Self { idx: 0, value } - } -} - -impl<'a> Iterator for GetAll<'a> { - type Item = &'a HeaderValue; - - fn next(&mut self) -> Option { - let val = self.value?; - - match val.get(self.idx) { - Some(val) => { - self.idx += 1; - Some(val) - } - None => { - // current index is none; remove value to fast-path future next calls - self.value = None; - None - } - } - } - - fn size_hint(&self) -> (usize, Option) { - match self.value { - Some(val) => (val.len(), Some(val.len())), - None => (0, Some(0)), - } - } -} - -impl ExactSizeIterator for GetAll<'_> {} - -impl iter::FusedIterator for GetAll<'_> {} - /// Iterator over removed, owned values with the same associated name. /// /// Returned from methods that remove or replace items. See [`HeaderMap::insert`] @@ -895,7 +852,7 @@ mod tests { assert_impl_all!(HeaderMap: IntoIterator); assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator); - assert_impl_all!(GetAll<'_>: Iterator, ExactSizeIterator, FusedIterator); + assert_impl_all!(std::slice::Iter<'_, HeaderValue>: Iterator, ExactSizeIterator, FusedIterator); assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator); assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator); assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator); diff --git a/src/response/response.rs b/src/response/response.rs index 4fb4b44b6..6fa2082e7 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -313,7 +313,7 @@ impl Future for HttpResponse { #[cfg(feature = "cookies")] pub struct CookieIter<'a> { - iter: header::map::GetAll<'a>, + iter: std::slice::Iter<'a, HeaderValue>, } #[cfg(feature = "cookies")] From 5c53db1e4ddb37e1e81ff5bb20e86d1ec87d6a44 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 18 Dec 2021 01:48:16 +0000 Subject: [PATCH 197/861] remove hidden anybody --- src/dev.rs | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/dev.rs b/src/dev.rs index edcc158f8..23a40f292 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -102,49 +102,3 @@ impl BodyEncoding for crate::HttpResponse { self } } - -// TODO: remove this if it doesn't appear to be needed - -#[allow(dead_code)] -#[derive(Debug)] -pub(crate) enum AnyBody { - None, - Full { body: crate::web::Bytes }, - Boxed { body: actix_http::body::BoxBody }, -} - -impl crate::body::MessageBody for AnyBody { - type Error = crate::BoxError; - - /// Body size hint. - fn size(&self) -> crate::body::BodySize { - match self { - AnyBody::None => crate::body::BodySize::None, - AnyBody::Full { body } => body.size(), - AnyBody::Boxed { body } => body.size(), - } - } - - /// Attempt to pull out the next chunk of body bytes. - fn poll_next( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll>> { - match self.get_mut() { - AnyBody::None => std::task::Poll::Ready(None), - AnyBody::Full { body } => { - let bytes = std::mem::take(body); - std::task::Poll::Ready(Some(Ok(bytes))) - } - AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx), - } - } - - fn try_into_bytes(self) -> Result { - match self { - AnyBody::None => Ok(crate::web::Bytes::new()), - AnyBody::Full { body } => Ok(body), - _ => Err(self), - } - } -} From d2b97240109b0f09e55900ad645c3cf6d5102956 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 18 Dec 2021 03:27:32 +0000 Subject: [PATCH 198/861] update bump script to detect prerelease versions --- scripts/bump | 14 ++++++++++++-- src/app.rs | 18 ++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/bump b/scripts/bump index 0c360569d..43cd8b8c7 100755 --- a/scripts/bump +++ b/scripts/bump @@ -55,6 +55,11 @@ else read -p "Update version to: " NEW_VERSION fi +# strip leading v from input +if [ "${NEW_VERSION:0:1}" = "v" ]; then + NEW_VERSION="${NEW_VERSION:1}" +fi + DATE="$(date -u +"%Y-%m-%d")" echo "updating from $CURRENT_VERSION => $NEW_VERSION ($DATE)" @@ -124,15 +129,20 @@ SHORT_PACKAGE_NAME="$(echo $PACKAGE_NAME | sed 's/^actix-web-//' | sed 's/^actix GIT_TAG="$(echo $SHORT_PACKAGE_NAME-v$NEW_VERSION)" RELEASE_TITLE="$(echo $PACKAGE_NAME: v$NEW_VERSION)" +if [ "$(echo $NEW_VERSION | grep beta)" ] || [ "$(echo $NEW_VERSION | grep rc)" ] || [ "$(echo $NEW_VERSION | grep alpha)" ]; then + PRERELEASE="--prerelease" +fi + echo echo "GitHub release command:" -echo "gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" --prerelease" +GH_CMD="gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" ${PRERELEASE:-}" +echo "$GH_CMD" read -p "Submit draft GH release: (y/N) " GH_RELEASE GH_RELEASE="${GH_RELEASE:-n}" if [ "$GH_RELEASE" = 'y' ] || [ "$GH_RELEASE" = 'Y' ]; then - gh release create "$GIT_TAG" --draft --title "$RELEASE_TITLE" --notes-file "$CHANGE_CHUNK_FILE" --prerelease + eval "$GH_CMD" fi echo diff --git a/src/app.rs b/src/app.rs index feb35d7ae..6bccc1ff1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -486,19 +486,21 @@ where #[cfg(test)] mod tests { - use actix_service::Service; + use actix_service::Service as _; use actix_utils::future::{err, ok}; use bytes::Bytes; use super::*; - use crate::http::{ - header::{self, HeaderValue}, - Method, StatusCode, + use crate::{ + http::{ + header::{self, HeaderValue}, + Method, StatusCode, + }, + middleware::DefaultHeaders, + service::ServiceRequest, + test::{call_service, init_service, read_body, try_init_service, TestRequest}, + web, HttpRequest, HttpResponse, }; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest}; - use crate::{web, HttpRequest, HttpResponse}; #[actix_rt::test] async fn test_default_resource() { From fb036264cc4ccaa9dd380b40f8b548a3353a7c36 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 18 Dec 2021 16:44:30 +0000 Subject: [PATCH 199/861] only build `SslConnectorBuilder` once (#2503) --- actix-http-test/src/lib.rs | 2 +- actix-test/src/lib.rs | 2 +- awc/CHANGES.md | 4 +++ awc/Cargo.toml | 2 +- awc/src/client/connector.rs | 69 ++++++++++++++++++++++++++---------- awc/tests/test_connector.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- tests/test_httpserver.rs | 2 +- 8 files changed, 61 insertions(+), 24 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index e7e479ab2..03239ece6 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -107,7 +107,7 @@ pub async fn test_server_with_addr>( Connector::new() .conn_lifetime(Duration::from_secs(0)) .timeout(Duration::from_millis(30000)) - .ssl(builder.build()) + .openssl(builder.build()) }; #[cfg(not(feature = "openssl"))] diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 3808ba69a..f86120f2f 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -340,7 +340,7 @@ where Connector::new() .conn_lifetime(Duration::from_secs(0)) .timeout(Duration::from_millis(30000)) - .ssl(builder.build()) + .openssl(builder.build()) } #[cfg(not(feature = "openssl"))] { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ef0f9faaa..7b822930c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +* Rename `Connector::{ssl => openssl}`. [#2503] +* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] + +[#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 4cab20d05..f9a541c7e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -62,7 +62,7 @@ actix-codec = "0.4.1" actix-service = "2.0.0" actix-http = "3.0.0-beta.16" actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] } +actix-tls = { version = "3.0.0-rc.2", features = ["connect", "uri"] } actix-utils = "3.0.0" ahash = "0.7" diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 40b3c4d32..423f656a8 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -22,11 +22,13 @@ use futures_core::{future::LocalBoxFuture, ready}; use http::Uri; use pin_project_lite::pin_project; -use super::config::ConnectorConfig; -use super::connection::{Connection, ConnectionIo}; -use super::error::ConnectError; -use super::pool::ConnectionPool; -use super::Connect; +use super::{ + config::ConnectorConfig, + connection::{Connection, ConnectionIo}, + error::ConnectError, + pool::ConnectionPool, + Connect, +}; enum OurTlsConnector { #[allow(dead_code)] // only dead when no TLS feature is enabled @@ -35,6 +37,12 @@ enum OurTlsConnector { #[cfg(feature = "openssl")] Openssl(actix_tls::connect::openssl::reexports::SslConnector), + /// Provided because building the OpenSSL context on newer versions can be very slow. + /// This prevents unnecessary calls to `.build()` while constructing the client connector. + #[cfg(feature = "openssl")] + #[allow(dead_code)] // false positive; used in build_ssl + OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder), + #[cfg(feature = "rustls")] Rustls(std::sync::Arc), } @@ -57,7 +65,7 @@ pub struct Connector { config: ConnectorConfig, #[allow(dead_code)] // only dead when no TLS feature is enabled - ssl: OurTlsConnector, + tls: OurTlsConnector, } impl Connector<()> { @@ -72,7 +80,7 @@ impl Connector<()> { Connector { connector: TcpConnector::new(resolver::resolver()).service(), config: ConnectorConfig::default(), - ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), + tls: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), } } @@ -116,7 +124,7 @@ impl Connector<()> { log::error!("Can not set ALPN protocol: {:?}", err); } - OurTlsConnector::Openssl(ssl.build()) + OurTlsConnector::OpensslBuilder(ssl) } } @@ -134,7 +142,7 @@ impl Connector { Connector { connector, config: self.config, - ssl: self.ssl, + tls: self.tls, } } } @@ -167,23 +175,35 @@ where self } + /// Use custom OpenSSL `SslConnector` instance. #[cfg(feature = "openssl")] - /// Use custom `SslConnector` instance. + pub fn openssl( + mut self, + connector: actix_tls::connect::openssl::reexports::SslConnector, + ) -> Self { + self.tls = OurTlsConnector::Openssl(connector); + self + } + + /// See docs for [`Connector::openssl`]. + #[doc(hidden)] + #[cfg(feature = "openssl")] + #[deprecated(since = "3.0.0", note = "Renamed to `Connector::openssl`.")] pub fn ssl( mut self, connector: actix_tls::connect::openssl::reexports::SslConnector, ) -> Self { - self.ssl = OurTlsConnector::Openssl(connector); + self.tls = OurTlsConnector::Openssl(connector); self } + /// Use custom Rustls `ClientConfig` instance. #[cfg(feature = "rustls")] - /// Use custom `ClientConfig` instance. pub fn rustls( mut self, connector: std::sync::Arc, ) -> Self { - self.ssl = OurTlsConnector::Rustls(connector); + self.tls = OurTlsConnector::Rustls(connector); self } @@ -198,7 +218,7 @@ where unimplemented!("actix-http client only supports versions http/1.1 & http/2") } }; - self.ssl = Connector::build_ssl(versions); + self.tls = Connector::build_ssl(versions); self } @@ -270,8 +290,8 @@ where } /// Finish configuration process and create connector service. - /// The Connector builder always concludes by calling `finish()` last in - /// its combinator chain. + /// + /// The `Connector` builder always concludes by calling `finish()` last in its combinator chain. pub fn finish(self) -> ConnectorService { let local_address = self.config.local_address; let timeout = self.config.timeout; @@ -284,7 +304,15 @@ where service: tcp_service_inner.clone(), }; - let tls_service = match self.ssl { + let tls = match self.tls { + #[cfg(feature = "openssl")] + OurTlsConnector::OpensslBuilder(builder) => { + OurTlsConnector::Openssl(builder.build()) + } + tls => tls, + }; + + let tls_service = match tls { OurTlsConnector::None => { #[cfg(not(feature = "dangerous-h2c"))] { @@ -374,6 +402,11 @@ where Some(actix_service::boxed::rc_service(tls_service)) } + #[cfg(feature = "openssl")] + OurTlsConnector::OpensslBuilder(_) => { + unreachable!("OpenSSL builder is built before this match."); + } + #[cfg(feature = "rustls")] OurTlsConnector::Rustls(tls) => { const H2: &[u8] = b"h2"; @@ -853,7 +886,7 @@ mod tests { let connector = Connector { connector: TcpConnector::new(resolver::resolver()).service(), config: ConnectorConfig::default(), - ssl: OurTlsConnector::None, + tls: OurTlsConnector::None, }; let client = Client::builder().connector(connector).finish(); diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 588c51463..0f0b81414 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -58,7 +58,7 @@ async fn test_connection_window_size() { .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); let client = awc::Client::builder() - .connector(awc::Connector::new().ssl(builder.build())) + .connector(awc::Connector::new().openssl(builder.build())) .initial_window_size(100) .initial_connection_window_size(100) .finish(); diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 811efd4bc..40c9ab8f0 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -72,7 +72,7 @@ async fn test_connection_reuse_h2() { .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); let client = awc::Client::builder() - .connector(awc::Connector::new().ssl(builder.build())) + .connector(awc::Connector::new().openssl(builder.build())) .finish(); // req 1 diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 887b51d41..464a650a2 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -121,7 +121,7 @@ async fn test_start_ssl() { let client = awc::Client::builder() .connector( awc::Connector::new() - .ssl(builder.build()) + .openssl(builder.build()) .timeout(Duration::from_millis(100)), ) .finish(); From 7d507a41ee9af8351d9dc028c48aadea627dadaa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 19 Dec 2021 03:58:29 +0000 Subject: [PATCH 200/861] Create FUNDING.yml --- .github/FUNDING.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..39e5d30cc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [robjtede] +open_collective: actix From 2e00776d5e650376cc0a8d3de4bf8a964a404c68 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 19 Dec 2021 04:18:57 +0000 Subject: [PATCH 201/861] Update FUNDING.yml --- .github/FUNDING.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 39e5d30cc..6164c657c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms github: [robjtede] -open_collective: actix From 17f636a1839850b0141ac0b697e8a74129f8a512 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 19 Dec 2021 17:05:27 +0000 Subject: [PATCH 202/861] split request and response modules (#2530) --- actix-http/CHANGES.md | 4 +- actix-http/src/h1/client.rs | 16 +- actix-http/src/h1/codec.rs | 18 +- actix-http/src/h1/decoder.rs | 23 +- actix-http/src/h1/encoder.rs | 22 +- actix-http/src/h1/expect.rs | 3 +- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/upgrade.rs | 4 +- actix-http/src/h1/utils.rs | 3 +- actix-http/src/lib.rs | 20 +- actix-http/src/message.rs | 382 +----------------- actix-http/src/payload.rs | 1 + actix-http/src/requests/head.rs | 174 ++++++++ actix-http/src/requests/mod.rs | 7 + actix-http/src/{ => requests}/request.rs | 8 +- .../builder.rs} | 4 +- actix-http/src/responses/head.rs | 208 ++++++++++ actix-http/src/responses/mod.rs | 11 + actix-http/src/{ => responses}/response.rs | 4 +- actix-http/src/ws/mod.rs | 2 +- src/app_service.rs | 1 + 21 files changed, 467 insertions(+), 450 deletions(-) create mode 100644 actix-http/src/requests/head.rs create mode 100644 actix-http/src/requests/mod.rs rename actix-http/src/{ => requests}/request.rs (97%) rename actix-http/src/{response_builder.rs => responses/builder.rs} (99%) create mode 100644 actix-http/src/responses/head.rs create mode 100644 actix-http/src/responses/mod.rs rename actix-http/src/{ => responses}/response.rs (99%) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c5e57e1a4..ad98d132a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx -### Removed -* `header::map::GetAll` iterator, its `Iterator::size_hint` method was wrongly implemented. Replaced with `std::slice::Iter`. [#2527] +### Changes +* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index bec167971..9bd896ae0 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -5,13 +5,15 @@ use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use http::{Method, Version}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder, reserve_readbuf}; -use super::{Message, MessageType}; -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::error::{ParseError, PayloadError}; -use crate::message::{ConnectionType, RequestHeadType, ResponseHead}; +use super::{ + decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, + encoder, reserve_readbuf, Message, MessageType, +}; +use crate::{ + body::BodySize, + error::{ParseError, PayloadError}, + ConnectionType, RequestHeadType, ResponseHead, ServiceConfig, +}; bitflags! { struct Flags: u8 { diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 29f6f4170..9a8907579 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -5,15 +5,13 @@ use bitflags::bitflags; use bytes::BytesMut; use http::{Method, Version}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; -use super::{Message, MessageType}; -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::error::ParseError; -use crate::message::ConnectionType; -use crate::request::Request; -use crate::response::Response; +use super::{ + decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, + encoder, Message, MessageType, +}; +use crate::{ + body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig, +}; bitflags! { struct Flags: u8 { @@ -199,7 +197,7 @@ mod tests { use http::Method; use super::*; - use crate::HttpMessage; + use crate::HttpMessage as _; #[actix_rt::test] async fn test_http_request_chunked_payload_and_next_message() { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index eb142f844..3d3a3ceac 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -2,17 +2,14 @@ use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Pol use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; -use http::header::{HeaderName, HeaderValue}; -use http::{header, Method, StatusCode, Uri, Version}; +use http::{ + header::{self, HeaderName, HeaderValue}, + Method, StatusCode, Uri, Version, +}; use log::{debug, error, trace}; use super::chunked::ChunkedState; -use crate::{ - error::ParseError, - header::HeaderMap, - message::{ConnectionType, ResponseHead}, - request::Request, -}; +use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead}; pub(crate) const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; @@ -50,7 +47,7 @@ pub(crate) enum PayloadLength { } pub(crate) trait MessageType: Sized { - fn set_connection_type(&mut self, ctype: Option); + fn set_connection_type(&mut self, conn_type: Option); fn set_expect(&mut self); @@ -193,8 +190,8 @@ pub(crate) trait MessageType: Sized { } impl MessageType for Request { - fn set_connection_type(&mut self, ctype: Option) { - if let Some(ctype) = ctype { + fn set_connection_type(&mut self, conn_type: Option) { + if let Some(ctype) = conn_type { self.head_mut().set_connection_type(ctype); } } @@ -278,8 +275,8 @@ impl MessageType for Request { } impl MessageType for ResponseHead { - fn set_connection_type(&mut self, ctype: Option) { - if let Some(ctype) = ctype { + fn set_connection_type(&mut self, conn_type: Option) { + if let Some(ctype) = conn_type { ResponseHead::set_connection_type(self, ctype); } } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 49bf5432d..f2a862278 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -1,19 +1,19 @@ -use std::io::Write; -use std::marker::PhantomData; -use std::ptr::copy_nonoverlapping; -use std::slice::from_raw_parts_mut; -use std::{cmp, io}; +use std::{ + cmp, + io::{self, Write as _}, + marker::PhantomData, + ptr::copy_nonoverlapping, + slice::from_raw_parts_mut, +}; use bytes::{BufMut, BytesMut}; use crate::{ body::BodySize, - config::ServiceConfig, - header::{map::Value, HeaderMap, HeaderName}, - header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, - helpers, - message::{ConnectionType, RequestHeadType}, - Response, StatusCode, Version, + header::{ + map::Value, HeaderMap, HeaderName, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, + }, + helpers, ConnectionType, RequestHeadType, Response, ServiceConfig, StatusCode, Version, }; const AVERAGE_HEADER_SIZE: usize = 30; diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index bb8e28e95..bef281e59 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,8 +1,7 @@ use actix_service::{Service, ServiceFactory}; use actix_utils::future::{ready, Ready}; -use crate::error::Error; -use crate::request::Request; +use crate::{Error, Request}; pub struct ExpectHandler; diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 17cbfb90f..64586a2dc 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -59,7 +59,7 @@ pub(crate) fn reserve_readbuf(src: &mut BytesMut) { #[cfg(test)] mod tests { use super::*; - use crate::request::Request; + use crate::Request; impl Message { pub fn message(self) -> Request { diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index e57ea8ae9..f25b0718b 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -2,9 +2,7 @@ use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; -use crate::error::Error; -use crate::h1::Codec; -use crate::request::Request; +use crate::{h1::Codec, Error, Request}; pub struct UpgradeHandler; diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index c8d79f0cd..131c7f1ed 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -9,9 +9,8 @@ use pin_project_lite::pin_project; use crate::{ body::{BodySize, MessageBody}, - error::Error, h1::{Codec, Message}, - response::Response, + Error, Response, }; pin_project! { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 60dc26f0f..ae822a055 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -31,23 +31,20 @@ extern crate log; pub mod body; mod builder; mod config; - #[cfg(feature = "__compress")] pub mod encoding; +pub mod error; mod extensions; +pub mod h1; +pub mod h2; pub mod header; mod helpers; mod http_message; mod message; mod payload; -mod request; -mod response; -mod response_builder; +mod requests; +mod responses; mod service; - -pub mod error; -pub mod h1; -pub mod h2; pub mod test; pub mod ws; @@ -58,11 +55,10 @@ pub use self::extensions::Extensions; pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; pub use self::message::ConnectionType; -pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; +pub use self::message::Message; pub use self::payload::{Payload, PayloadStream}; -pub use self::request::Request; -pub use self::response::Response; -pub use self::response_builder::ResponseBuilder; +pub use self::requests::{Request, RequestHead, RequestHeadType}; +pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; pub use ::http::{uri, uri::Uri}; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 31c2db718..2692a4bee 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,16 +1,7 @@ -use std::{ - cell::{Ref, RefCell, RefMut}, - net, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use bitflags::bitflags; -use crate::{ - header::{self, HeaderMap}, - Extensions, Method, StatusCode, Uri, Version, -}; - /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] pub enum ConnectionType { @@ -44,294 +35,6 @@ pub trait Head: Default + 'static { F: FnOnce(&MessagePool) -> R; } -#[derive(Debug, Clone)] -pub struct RequestHead { - pub method: Method, - pub uri: Uri, - pub version: Version, - pub headers: HeaderMap, - pub peer_addr: Option, - flags: Flags, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - method: Method::default(), - uri: Uri::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - peer_addr: None, - flags: Flags::empty(), - } - } -} - -impl Head for RequestHead { - fn clear(&mut self) { - self.flags = Flags::empty(); - self.headers.clear(); - } - - fn with_pool(f: F) -> R - where - F: FnOnce(&MessagePool) -> R, - { - REQUEST_POOL.with(|p| f(p)) - } -} - -impl RequestHead { - /// Read the message headers. - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Mutable reference to the message headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// Is to uppercase headers with Camel-Case. - /// Default is `false` - #[inline] - pub fn camel_case_headers(&self) -> bool { - self.flags.contains(Flags::CAMEL_CASE) - } - - /// Set `true` to send headers which are formatted as Camel-Case. - #[inline] - pub fn set_camel_case_headers(&mut self, val: bool) { - if val { - self.flags.insert(Flags::CAMEL_CASE); - } else { - self.flags.remove(Flags::CAMEL_CASE); - } - } - - #[inline] - /// Set connection type of the message - pub fn set_connection_type(&mut self, ctype: ConnectionType) { - match ctype { - ConnectionType::Close => self.flags.insert(Flags::CLOSE), - ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), - ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), - } - } - - #[inline] - /// Connection type - pub fn connection_type(&self) -> ConnectionType { - if self.flags.contains(Flags::CLOSE) { - ConnectionType::Close - } else if self.flags.contains(Flags::KEEP_ALIVE) { - ConnectionType::KeepAlive - } else if self.flags.contains(Flags::UPGRADE) { - ConnectionType::Upgrade - } else if self.version < Version::HTTP_11 { - ConnectionType::Close - } else { - ConnectionType::KeepAlive - } - } - - /// Connection upgrade status - pub fn upgrade(&self) -> bool { - self.headers() - .get(header::CONNECTION) - .map(|hdr| { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("upgrade") - } else { - false - } - }) - .unwrap_or(false) - } - - #[inline] - /// Get response body chunking state - pub fn chunked(&self) -> bool { - !self.flags.contains(Flags::NO_CHUNKING) - } - - #[inline] - pub fn no_chunking(&mut self, val: bool) { - if val { - self.flags.insert(Flags::NO_CHUNKING); - } else { - self.flags.remove(Flags::NO_CHUNKING); - } - } - - #[inline] - /// Request contains `EXPECT` header - pub fn expect(&self) -> bool { - self.flags.contains(Flags::EXPECT) - } - - #[inline] - pub(crate) fn set_expect(&mut self) { - self.flags.insert(Flags::EXPECT); - } -} - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum RequestHeadType { - Owned(RequestHead), - Rc(Rc, Option), -} - -impl RequestHeadType { - pub fn extra_headers(&self) -> Option<&HeaderMap> { - match self { - RequestHeadType::Owned(_) => None, - RequestHeadType::Rc(_, headers) => headers.as_ref(), - } - } -} - -impl AsRef for RequestHeadType { - fn as_ref(&self) -> &RequestHead { - match self { - RequestHeadType::Owned(head) => head, - RequestHeadType::Rc(head, _) => head.as_ref(), - } - } -} - -impl From for RequestHeadType { - fn from(head: RequestHead) -> Self { - RequestHeadType::Owned(head) - } -} - -#[derive(Debug)] -pub struct ResponseHead { - pub version: Version, - pub status: StatusCode, - pub headers: HeaderMap, - pub reason: Option<&'static str>, - pub(crate) extensions: RefCell, - flags: Flags, -} - -impl ResponseHead { - /// Create new instance of `ResponseHead` type - #[inline] - pub fn new(status: StatusCode) -> ResponseHead { - ResponseHead { - status, - version: Version::default(), - headers: HeaderMap::with_capacity(12), - reason: None, - flags: Flags::empty(), - extensions: RefCell::new(Extensions::new()), - } - } - - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.extensions.borrow_mut() - } - - #[inline] - /// Read the message headers. - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - #[inline] - /// Mutable reference to the message headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - #[inline] - /// Set connection type of the message - pub fn set_connection_type(&mut self, ctype: ConnectionType) { - match ctype { - ConnectionType::Close => self.flags.insert(Flags::CLOSE), - ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), - ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), - } - } - - #[inline] - pub fn connection_type(&self) -> ConnectionType { - if self.flags.contains(Flags::CLOSE) { - ConnectionType::Close - } else if self.flags.contains(Flags::KEEP_ALIVE) { - ConnectionType::KeepAlive - } else if self.flags.contains(Flags::UPGRADE) { - ConnectionType::Upgrade - } else if self.version < Version::HTTP_11 { - ConnectionType::Close - } else { - ConnectionType::KeepAlive - } - } - - /// Check if keep-alive is enabled - #[inline] - pub fn keep_alive(&self) -> bool { - self.connection_type() == ConnectionType::KeepAlive - } - - /// Check upgrade status of this message - #[inline] - pub fn upgrade(&self) -> bool { - self.connection_type() == ConnectionType::Upgrade - } - - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - self.reason.unwrap_or_else(|| { - self.status - .canonical_reason() - .unwrap_or("") - }) - } - - #[inline] - pub(crate) fn conn_type(&self) -> Option { - if self.flags.contains(Flags::CLOSE) { - Some(ConnectionType::Close) - } else if self.flags.contains(Flags::KEEP_ALIVE) { - Some(ConnectionType::KeepAlive) - } else if self.flags.contains(Flags::UPGRADE) { - Some(ConnectionType::Upgrade) - } else { - None - } - } - - #[inline] - /// Get response body chunking state - pub fn chunked(&self) -> bool { - !self.flags.contains(Flags::NO_CHUNKING) - } - - #[inline] - /// Set no chunking for payload - pub fn no_chunking(&mut self, val: bool) { - if val { - self.flags.insert(Flags::NO_CHUNKING); - } else { - self.flags.remove(Flags::NO_CHUNKING); - } - } -} - pub struct Message { /// Rc here should not be cloned by anyone. /// It's used to reuse allocation of T and no shared ownership is allowed. @@ -365,53 +68,12 @@ impl Drop for Message { } } -pub(crate) struct BoxedResponseHead { - head: Option>, -} - -impl BoxedResponseHead { - /// Get new message from the pool of objects - pub fn new(status: StatusCode) -> Self { - RESPONSE_POOL.with(|p| p.get_message(status)) - } -} - -impl std::ops::Deref for BoxedResponseHead { - type Target = ResponseHead; - - fn deref(&self) -> &Self::Target { - self.head.as_ref().unwrap() - } -} - -impl std::ops::DerefMut for BoxedResponseHead { - fn deref_mut(&mut self) -> &mut Self::Target { - self.head.as_mut().unwrap() - } -} - -impl Drop for BoxedResponseHead { - fn drop(&mut self) { - if let Some(head) = self.head.take() { - RESPONSE_POOL.with(move |p| p.release(head)) - } - } -} - #[doc(hidden)] /// Request's objects pool pub struct MessagePool(RefCell>>); -#[doc(hidden)] -#[allow(clippy::vec_box)] -/// Request's objects pool -pub struct BoxedResponsePool(RefCell>>); - -thread_local!(static REQUEST_POOL: MessagePool = MessagePool::::create()); -thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create()); - impl MessagePool { - fn create() -> MessagePool { + pub(crate) fn create() -> MessagePool { MessagePool(RefCell::new(Vec::with_capacity(128))) } @@ -433,43 +95,11 @@ impl MessagePool { } #[inline] - /// Release request instance + /// Release message instance fn release(&self, msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - v.push(msg); - } - } -} - -impl BoxedResponsePool { - fn create() -> BoxedResponsePool { - BoxedResponsePool(RefCell::new(Vec::with_capacity(128))) - } - - /// Get message from the pool - #[inline] - fn get_message(&self, status: StatusCode) -> BoxedResponseHead { - if let Some(mut head) = self.0.borrow_mut().pop() { - head.reason = None; - head.status = status; - head.headers.clear(); - head.flags = Flags::empty(); - BoxedResponseHead { head: Some(head) } - } else { - BoxedResponseHead { - head: Some(Box::new(ResponseHead::new(status))), - } - } - } - - #[inline] - /// Release request instance - fn release(&self, mut msg: Box) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - msg.extensions.get_mut().clear(); - v.push(msg); + let pool = &mut self.0.borrow_mut(); + if pool.len() < 128 { + pool.push(msg); } } } diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 5734af341..89a1a2db1 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -7,6 +7,7 @@ use h2::RecvStream; use crate::error::PayloadError; +// TODO: rename to boxed payload /// A boxed payload. pub type PayloadStream = Pin>>>; diff --git a/actix-http/src/requests/head.rs b/actix-http/src/requests/head.rs new file mode 100644 index 000000000..524075b61 --- /dev/null +++ b/actix-http/src/requests/head.rs @@ -0,0 +1,174 @@ +use std::{net, rc::Rc}; + +use crate::{ + header::{self, HeaderMap}, + message::{Flags, Head, MessagePool}, + ConnectionType, Method, Uri, Version, +}; + +thread_local! { + static REQUEST_POOL: MessagePool = MessagePool::::create() +} + +#[derive(Debug, Clone)] +pub struct RequestHead { + pub method: Method, + pub uri: Uri, + pub version: Version, + pub headers: HeaderMap, + pub peer_addr: Option, + flags: Flags, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { + method: Method::default(), + uri: Uri::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + peer_addr: None, + flags: Flags::empty(), + } + } +} + +impl Head for RequestHead { + fn clear(&mut self) { + self.flags = Flags::empty(); + self.headers.clear(); + } + + fn with_pool(f: F) -> R + where + F: FnOnce(&MessagePool) -> R, + { + REQUEST_POOL.with(|p| f(p)) + } +} + +impl RequestHead { + /// Read the message headers. + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + /// Mutable reference to the message headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + /// Is to uppercase headers with Camel-Case. + /// Default is `false` + #[inline] + pub fn camel_case_headers(&self) -> bool { + self.flags.contains(Flags::CAMEL_CASE) + } + + /// Set `true` to send headers which are formatted as Camel-Case. + #[inline] + pub fn set_camel_case_headers(&mut self, val: bool) { + if val { + self.flags.insert(Flags::CAMEL_CASE); + } else { + self.flags.remove(Flags::CAMEL_CASE); + } + } + + #[inline] + /// Set connection type of the message + pub fn set_connection_type(&mut self, ctype: ConnectionType) { + match ctype { + ConnectionType::Close => self.flags.insert(Flags::CLOSE), + ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), + ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), + } + } + + #[inline] + /// Connection type + pub fn connection_type(&self) -> ConnectionType { + if self.flags.contains(Flags::CLOSE) { + ConnectionType::Close + } else if self.flags.contains(Flags::KEEP_ALIVE) { + ConnectionType::KeepAlive + } else if self.flags.contains(Flags::UPGRADE) { + ConnectionType::Upgrade + } else if self.version < Version::HTTP_11 { + ConnectionType::Close + } else { + ConnectionType::KeepAlive + } + } + + /// Connection upgrade status + pub fn upgrade(&self) -> bool { + self.headers() + .get(header::CONNECTION) + .map(|hdr| { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + }) + .unwrap_or(false) + } + + #[inline] + /// Get response body chunking state + pub fn chunked(&self) -> bool { + !self.flags.contains(Flags::NO_CHUNKING) + } + + #[inline] + pub fn no_chunking(&mut self, val: bool) { + if val { + self.flags.insert(Flags::NO_CHUNKING); + } else { + self.flags.remove(Flags::NO_CHUNKING); + } + } + + #[inline] + /// Request contains `EXPECT` header + pub fn expect(&self) -> bool { + self.flags.contains(Flags::EXPECT) + } + + #[inline] + pub(crate) fn set_expect(&mut self) { + self.flags.insert(Flags::EXPECT); + } +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum RequestHeadType { + Owned(RequestHead), + Rc(Rc, Option), +} + +impl RequestHeadType { + pub fn extra_headers(&self) -> Option<&HeaderMap> { + match self { + RequestHeadType::Owned(_) => None, + RequestHeadType::Rc(_, headers) => headers.as_ref(), + } + } +} + +impl AsRef for RequestHeadType { + fn as_ref(&self) -> &RequestHead { + match self { + RequestHeadType::Owned(head) => head, + RequestHeadType::Rc(head, _) => head.as_ref(), + } + } +} + +impl From for RequestHeadType { + fn from(head: RequestHead) -> Self { + RequestHeadType::Owned(head) + } +} diff --git a/actix-http/src/requests/mod.rs b/actix-http/src/requests/mod.rs new file mode 100644 index 000000000..fc35da65a --- /dev/null +++ b/actix-http/src/requests/mod.rs @@ -0,0 +1,7 @@ +//! HTTP requests. + +mod head; +mod request; + +pub use self::head::{RequestHead, RequestHeadType}; +pub use self::request::Request; diff --git a/actix-http/src/request.rs b/actix-http/src/requests/request.rs similarity index 97% rename from actix-http/src/request.rs rename to actix-http/src/requests/request.rs index c7752d470..74347fbc2 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/requests/request.rs @@ -10,11 +10,7 @@ use std::{ use http::{header, Method, Uri, Version}; use crate::{ - extensions::Extensions, - header::HeaderMap, - message::{Message, RequestHead}, - payload::{Payload, PayloadStream}, - HttpMessage, + header::HeaderMap, Extensions, HttpMessage, Message, Payload, PayloadStream, RequestHead, }; /// An HTTP request. @@ -206,7 +202,7 @@ impl

Request

{ /// Returns the request data container, leaving an empty one in it's place. pub fn take_req_data(&mut self) -> Extensions { - mem::take(&mut self.req_data.get_mut()) + mem::take(self.req_data.get_mut()) } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/responses/builder.rs similarity index 99% rename from actix-http/src/response_builder.rs rename to actix-http/src/responses/builder.rs index adbe86fca..5854863de 100644 --- a/actix-http/src/response_builder.rs +++ b/actix-http/src/responses/builder.rs @@ -9,8 +9,8 @@ use crate::{ body::{EitherBody, MessageBody}, error::{Error, HttpError}, header::{self, TryIntoHeaderPair, TryIntoHeaderValue}, - message::{BoxedResponseHead, ConnectionType, ResponseHead}, - Extensions, Response, StatusCode, + responses::{BoxedResponseHead, ResponseHead}, + ConnectionType, Extensions, Response, StatusCode, }; /// An HTTP response builder. diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs new file mode 100644 index 000000000..78d9536e5 --- /dev/null +++ b/actix-http/src/responses/head.rs @@ -0,0 +1,208 @@ +//! Response head type and caching pool. + +use std::{ + cell::{Ref, RefCell, RefMut}, + ops, +}; + +use crate::{ + header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version, +}; + +thread_local! { + static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create(); +} + +#[derive(Debug)] +pub struct ResponseHead { + pub version: Version, + pub status: StatusCode, + pub headers: HeaderMap, + pub reason: Option<&'static str>, + pub(crate) extensions: RefCell, + flags: Flags, +} + +impl ResponseHead { + /// Create new instance of `ResponseHead` type + #[inline] + pub fn new(status: StatusCode) -> ResponseHead { + ResponseHead { + status, + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(12), + reason: None, + flags: Flags::empty(), + extensions: RefCell::new(Extensions::new()), + } + } + + #[inline] + /// Read the message headers. + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + #[inline] + /// Mutable reference to the message headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { + self.extensions.borrow_mut() + } + + #[inline] + /// Set connection type of the message + pub fn set_connection_type(&mut self, ctype: ConnectionType) { + match ctype { + ConnectionType::Close => self.flags.insert(Flags::CLOSE), + ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), + ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), + } + } + + #[inline] + pub fn connection_type(&self) -> ConnectionType { + if self.flags.contains(Flags::CLOSE) { + ConnectionType::Close + } else if self.flags.contains(Flags::KEEP_ALIVE) { + ConnectionType::KeepAlive + } else if self.flags.contains(Flags::UPGRADE) { + ConnectionType::Upgrade + } else if self.version < Version::HTTP_11 { + ConnectionType::Close + } else { + ConnectionType::KeepAlive + } + } + + /// Check if keep-alive is enabled + #[inline] + pub fn keep_alive(&self) -> bool { + self.connection_type() == ConnectionType::KeepAlive + } + + /// Check upgrade status of this message + #[inline] + pub fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + self.reason.unwrap_or_else(|| { + self.status + .canonical_reason() + .unwrap_or("") + }) + } + + #[inline] + pub(crate) fn conn_type(&self) -> Option { + if self.flags.contains(Flags::CLOSE) { + Some(ConnectionType::Close) + } else if self.flags.contains(Flags::KEEP_ALIVE) { + Some(ConnectionType::KeepAlive) + } else if self.flags.contains(Flags::UPGRADE) { + Some(ConnectionType::Upgrade) + } else { + None + } + } + + #[inline] + /// Get response body chunking state + pub fn chunked(&self) -> bool { + !self.flags.contains(Flags::NO_CHUNKING) + } + + #[inline] + /// Set no chunking for payload + pub fn no_chunking(&mut self, val: bool) { + if val { + self.flags.insert(Flags::NO_CHUNKING); + } else { + self.flags.remove(Flags::NO_CHUNKING); + } + } +} + +pub(crate) struct BoxedResponseHead { + head: Option>, +} + +impl BoxedResponseHead { + /// Get new message from the pool of objects + pub fn new(status: StatusCode) -> Self { + RESPONSE_POOL.with(|p| p.get_message(status)) + } +} + +impl ops::Deref for BoxedResponseHead { + type Target = ResponseHead; + + fn deref(&self) -> &Self::Target { + self.head.as_ref().unwrap() + } +} + +impl ops::DerefMut for BoxedResponseHead { + fn deref_mut(&mut self) -> &mut Self::Target { + self.head.as_mut().unwrap() + } +} + +impl Drop for BoxedResponseHead { + fn drop(&mut self) { + if let Some(head) = self.head.take() { + RESPONSE_POOL.with(move |p| p.release(head)) + } + } +} + +/// Request's objects pool +#[doc(hidden)] +pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell>>); + +impl BoxedResponsePool { + fn create() -> BoxedResponsePool { + BoxedResponsePool(RefCell::new(Vec::with_capacity(128))) + } + + /// Get message from the pool + #[inline] + fn get_message(&self, status: StatusCode) -> BoxedResponseHead { + if let Some(mut head) = self.0.borrow_mut().pop() { + head.reason = None; + head.status = status; + head.headers.clear(); + head.flags = Flags::empty(); + BoxedResponseHead { head: Some(head) } + } else { + BoxedResponseHead { + head: Some(Box::new(ResponseHead::new(status))), + } + } + } + + /// Release request instance + #[inline] + fn release(&self, mut msg: Box) { + let pool = &mut self.0.borrow_mut(); + if pool.len() < 128 { + msg.extensions.get_mut().clear(); + pool.push(msg); + } + } +} diff --git a/actix-http/src/responses/mod.rs b/actix-http/src/responses/mod.rs new file mode 100644 index 000000000..899232b9f --- /dev/null +++ b/actix-http/src/responses/mod.rs @@ -0,0 +1,11 @@ +//! HTTP response. + +mod builder; +mod head; +#[allow(clippy::module_inception)] +mod response; + +pub use self::builder::ResponseBuilder; +pub(crate) use self::head::BoxedResponseHead; +pub use self::head::ResponseHead; +pub use self::response::Response; diff --git a/actix-http/src/response.rs b/actix-http/src/responses/response.rs similarity index 99% rename from actix-http/src/response.rs rename to actix-http/src/responses/response.rs index a0e6d9b7c..d781bdfaa 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/responses/response.rs @@ -12,8 +12,8 @@ use crate::{ body::{BoxBody, MessageBody}, extensions::Extensions, header::{self, HeaderMap, TryIntoHeaderValue}, - message::{BoxedResponseHead, ResponseHead}, - Error, ResponseBuilder, StatusCode, + responses::{BoxedResponseHead, ResponseBuilder, ResponseHead}, + Error, StatusCode, }; /// An HTTP response. diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index c23d4edfc..6491da149 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,7 +9,7 @@ use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::body::BoxBody; -use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder}; +use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder}; mod codec; mod dispatcher; diff --git a/src/app_service.rs b/src/app_service.rs index 515693db4..4e84cb201 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -221,6 +221,7 @@ where req_data, ) }; + self.service.call(ServiceRequest::new(req, payload)) } } From 64c2e5e1cd4c3c2315139fafa7f7e28b2ac6de07 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:16:07 +0000 Subject: [PATCH 203/861] remove crate level clippy lint --- actix-http/src/builder.rs | 1 + actix-http/src/lib.rs | 7 +++---- actix-http/src/message.rs | 1 + actix-http/src/payload.rs | 6 ++++-- actix-http/src/requests/request.rs | 1 + actix-http/src/responses/response.rs | 5 ++--- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 1b5da20b6..408ee7924 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -36,6 +36,7 @@ where >::Future: 'static, { /// Create instance of `ServiceConfigBuilder` + #[allow(clippy::new_without_default)] pub fn new() -> Self { HttpServiceBuilder { keep_alive: KeepAlive::Timeout(5), diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ae822a055..2b7bc730b 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -19,7 +19,6 @@ #![allow( clippy::type_complexity, clippy::too_many_arguments, - clippy::new_without_default, clippy::borrow_interior_mutable_const )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] @@ -28,6 +27,9 @@ #[macro_use] extern crate log; +pub use ::http::{uri, uri::Uri}; +pub use ::http::{Method, StatusCode, Version}; + pub mod body; mod builder; mod config; @@ -61,9 +63,6 @@ pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; -pub use ::http::{uri, uri::Uri}; -pub use ::http::{Method, StatusCode, Version}; - /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 2692a4bee..34213f68a 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -43,6 +43,7 @@ pub struct Message { impl Message { /// Get new message from the pool of objects + #[allow(clippy::new_without_default)] pub fn new() -> Self { T::with_pool(MessagePool::get_message) } diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 89a1a2db1..69840e7c1 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,5 +1,7 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use bytes::Bytes; use futures_core::Stream; diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 74347fbc2..0254a8f11 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -59,6 +59,7 @@ impl From> for Request { impl Request { /// Create new Request instance + #[allow(clippy::new_without_default)] pub fn new() -> Request { Request { head: Message::new(), diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index d781bdfaa..ec9157afb 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -10,10 +10,9 @@ use bytestring::ByteString; use crate::{ body::{BoxBody, MessageBody}, - extensions::Extensions, header::{self, HeaderMap, TryIntoHeaderValue}, - responses::{BoxedResponseHead, ResponseBuilder, ResponseHead}, - Error, StatusCode, + responses::BoxedResponseHead, + Error, Extensions, ResponseBuilder, ResponseHead, StatusCode, }; /// An HTTP response. From f8488aff1e348fca884ebe0f58b90be82a24a2e5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:20:53 +0000 Subject: [PATCH 204/861] upstream changelog for v3.3.3 --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 857974d3f..77ab2e218 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -318,6 +318,14 @@ [#1875]: https://github.com/actix/actix-web/pull/1875 [#1878]: https://github.com/actix/actix-web/pull/1878 + +## 3.3.3 - 2021-12-18 +### Changed +* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] + +[#2529]: https://github.com/actix/actix-web/pull/2529 + + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] From 40a016207461d2ec2167f5614709da85994216e3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 07:58:37 +0000 Subject: [PATCH 205/861] add tests to scope and resource for returning from fns --- src/app.rs | 36 ++++++++++++++++++------------------ src/resource.rs | 32 +++++++++++++++++++++++++++++++- src/scope.rs | 31 ++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6bccc1ff1..b4b952734 100644 --- a/src/app.rs +++ b/src/app.rs @@ -709,24 +709,24 @@ mod tests { assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } - /// compile-only test for returning app type from function - pub fn foreign_app_type() -> App< - impl ServiceFactory< - ServiceRequest, - Response = ServiceResponse, - Config = (), - InitError = (), - Error = Error, - >, - > { - App::new() - // logger can be removed without affecting the return type - .wrap(crate::middleware::Logger::default()) - .route("/", web::to(|| async { "hello" })) - } - #[test] - fn return_foreign_app_type() { - let _app = foreign_app_type(); + fn can_be_returned_from_fn() { + /// compile-only test for returning app type from function + pub fn my_app() -> App< + impl ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Config = (), + InitError = (), + Error = Error, + >, + > { + App::new() + // logger can be removed without affecting the return type + .wrap(crate::middleware::Logger::default()) + .route("/", web::to(|| async { "hello" })) + } + + let _ = init_service(my_app()); } } diff --git a/src/resource.rs b/src/resource.rs index 53104930a..c13544063 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -505,18 +505,48 @@ mod tests { use actix_service::Service; use actix_utils::future::ok; + use super::*; use crate::{ guard, http::{ header::{self, HeaderValue}, Method, StatusCode, }, - middleware::DefaultHeaders, + middleware::{Compat, DefaultHeaders}, service::{ServiceRequest, ServiceResponse}, test::{call_service, init_service, TestRequest}, web, App, Error, HttpMessage, HttpResponse, }; + #[test] + fn can_be_returned_from_fn() { + fn my_resource() -> Resource { + web::resource("/test").route(web::get().to(|| async { "hello" })) + } + + fn my_compat_resource() -> Resource< + impl ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > { + web::resource("/test-compat") + // .wrap_fn(|req, srv| { + // let fut = srv.call(req); + // async { Ok(fut.await?.map_into_right_body::<()>()) } + // }) + .wrap(Compat::new(DefaultHeaders::new())) + .route(web::get().to(|| async { "hello" })) + } + + App::new() + .service(my_resource()) + .service(my_compat_resource()); + } + #[actix_rt::test] async fn test_middleware() { let srv = init_service( diff --git a/src/scope.rs b/src/scope.rs index c35584770..35bbb50ba 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -586,18 +586,47 @@ mod tests { use actix_utils::future::ok; use bytes::Bytes; + use super::*; use crate::{ guard, http::{ header::{self, HeaderValue}, Method, StatusCode, }, - middleware::DefaultHeaders, + middleware::{Compat, DefaultHeaders}, service::{ServiceRequest, ServiceResponse}, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, web, App, HttpMessage, HttpRequest, HttpResponse, }; + #[test] + fn can_be_returned_from_fn() { + fn my_scope() -> Scope { + web::scope("/test") + .service(web::resource("").route(web::get().to(|| async { "hello" }))) + } + + fn my_compat_scope() -> Scope< + impl ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > { + web::scope("/test-compat") + // .wrap_fn(|req, srv| { + // let fut = srv.call(req); + // async { Ok(fut.await?.map_into_right_body::<()>()) } + // }) + .wrap(Compat::new(DefaultHeaders::new())) + .service(web::resource("").route(web::get().to(|| async { "hello" }))) + } + + App::new().service(my_scope()).service(my_compat_scope()); + } + #[actix_rt::test] async fn test_scope() { let srv = From 1ea619f2a1722206cddf4af0a43715fc8202a06e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:17:35 +0000 Subject: [PATCH 206/861] use dash hyphenation in changelogs --- CHANGES.md | 551 ++++++++++++++++++----------------- actix-files/CHANGES.md | 102 +++---- actix-http-test/CHANGES.md | 76 ++--- actix-http/CHANGES.md | 544 +++++++++++++++++----------------- actix-multipart/CHANGES.md | 74 ++--- actix-router/CHANGES.md | 102 +++---- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 ++-- actix-web-codegen/CHANGES.md | 52 ++-- awc/CHANGES.md | 170 +++++------ 10 files changed, 877 insertions(+), 876 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 77ab2e218..0458958c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,29 +2,29 @@ ## Unreleased - 2021-xx-xx - +- ## 4.0.0-beta.15 - 2021-12-17 ### Added * Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] * Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] * Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] * Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -* Relax body type and error bounds on test utilities. [#2518] - -### Removed -* Top-level `EitherExtractError` export. [#2510] -* Conversion implementations for `either` crate. [#2516] +- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +- Relax body type and error bounds on test utilities. [#2518] +- +- # Removed +- Top-level `EitherExtractError` export. [#2510] +- Conversion implementations for `either` crate. [#2516] * `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] -[#2510]: https://github.com/actix/actix-web/pull/2510 -[#2515]: https://github.com/actix/actix-web/pull/2515 -[#2516]: https://github.com/actix/actix-web/pull/2516 +- 2510]: https://github.com/actix/actix-web/pull/2510 +- 2515]: https://github.com/actix/actix-web/pull/2515 +- 2516]: https://github.com/actix/actix-web/pull/2516 [#2518]: https://github.com/actix/actix-web/pull/2518 @@ -34,31 +34,31 @@ * `AcceptEncoding` typed header. [#2482] * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -* `HttpRequest::{req_data,req_data_mut}`. [#2487] -* `ServiceResponse::into_parts`. [#2499] - -### Changed -* Rename `Accept::{mime_precedence => ranked}`. [#2480] -* Rename `Accept::{mime_preference => preference}`. [#2480] +- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +- `HttpRequest::{req_data,req_data_mut}`. [#2487] +- `ServiceResponse::into_parts`. [#2499] +- +- # Changed +- Rename `Accept::{mime_precedence => ranked}`. [#2480] +- Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -* Remove `B` (body) type parameter on `App`. [#2493] -* Add `B` (body) type parameter on `Scope`. [#2492] -* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] - -### Fixed -* Accept wildcard `*` items in `AcceptLanguage`. [#2480] -* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +- Remove `B` (body) type parameter on `App`. [#2493] +- Add `B` (body) type parameter on `Scope`. [#2492] +- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] +- +- # Fixed +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] -### Removed -* `ConnectionInfo::get`. [#2487] - +- # Removed +- `ConnectionInfo::get`. [#2487] +- [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 -[#2480]: https://github.com/actix/actix-web/pull/2480 +- 2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 @@ -75,20 +75,20 @@ [#2474]: https://github.com/actix/actix-web/pull/2474 - +- ## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed * Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] - +- ### Removed * `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] - +- [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 - +- ## 4.0.0-beta.11 - 2021-11-15 ### Added @@ -96,11 +96,11 @@ ### Changed * `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 -[#2442]: https://github.com/actix/actix-web/pull/2442 - +- 2442]: https://github.com/actix/actix-web/pull/2442 +- ## 4.0.0-beta.10 - 2021-10-20 ### Added @@ -108,18 +108,18 @@ * `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -* Associated type `FromRequest::Config` was removed. [#2233] -* Inner field made private on `web::Payload`. [#2384] +- Associated type `FromRequest::Config` was removed. [#2233] +- Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] * Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. - -### Removed -* Useless `ServiceResponse::checked_expr` method. [#2401] - +- Minimum supported Rust version (MSRV) is now 1.52. +- +- # Removed +- Useless `ServiceResponse::checked_expr` method. [#2401] +- [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 -[#2384]: https://github.com/actix/actix-web/pull/2384 +- 2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 [#2403]: https://github.com/actix/actix-web/pull/2403 [#2409]: https://github.com/actix/actix-web/pull/2409 @@ -132,17 +132,17 @@ ### Changed * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -* Move `BaseHttpResponse` to `dev::Response`. [#2379] +- Move `BaseHttpResponse` to `dev::Response`. [#2379] * Enable `TestRequest::param` to accept more than just static strings. [#2172] * Minimum supported Rust version (MSRV) is now 1.51. - -### Fixed -* Fix quality parse error in Accept-Encoding header. [#2344] -* Re-export correct type at `web::HttpResponse`. [#2379] +- +- # Fixed +- Fix quality parse error in Accept-Encoding header. [#2344] +- Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 -[#2325]: https://github.com/actix/actix-web/pull/2325 -[#2344]: https://github.com/actix/actix-web/pull/2344 +- 2325]: https://github.com/actix/actix-web/pull/2325 +- 2344]: https://github.com/actix/actix-web/pull/2344 [#2379]: https://github.com/actix/actix-web/pull/2379 @@ -152,18 +152,18 @@ * Add extractors for `Uri` and `Method`. [#2263] * Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] * Add `Route::service` for using hand-written services as handlers. [#2262] - -### Changed -* Change compression algorithm features flags. [#2250] -* Deprecate `App::data` and `App::data_factory`. [#2271] +- +- # Changed +- Change compression algorithm features flags. [#2250] +- Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] -### Fixed -* Scope and Resource middleware can access data items set on their own layer. [#2288] - +- # Fixed +- Scope and Resource middleware can access data items set on their own layer. [#2288] +- [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 -[#2271]: https://github.com/actix/actix-web/pull/2271 +- 2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 [#2282]: https://github.com/actix/actix-web/pull/2282 @@ -176,23 +176,23 @@ ### Changed * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -[#2162]: (https://github.com/actix/actix-web/pull/2162) +- 2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] * `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -* Update `language-tags` to `0.3`. +- Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] -* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] - -### Removed -* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] - +- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] +- +- # Removed +- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] +- [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 -[#2253]: https://github.com/actix/actix-web/pull/2253 +- 2253]: https://github.com/actix/actix-web/pull/2253 [#2246]: https://github.com/actix/actix-web/pull/2246 @@ -202,11 +202,11 @@ ### Changed * Most error types are now marked `#[non_exhaustive]`. [#2148] -* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 -[#2148]: https://github.com/actix/actix-web/pull/2148 - +- 2148]: https://github.com/actix/actix-web/pull/2148 +- ## 4.0.0-beta.5 - 2021-04-02 ### Added @@ -214,20 +214,20 @@ * Added `TestServer::client_headers` method. [#2097] ### Fixed -* Double ampersand in Logger format is escaped correctly. [#2067] - +- Double ampersand in Logger format is escaped correctly. [#2067] +- ### Changed * `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed - instead of skipping. (Only the first error is kept when multiple error occur) [#2093] +- instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -* The `client` mod was removed. Clients should now use `awc` directly. +- The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) * Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] - +- [#2067]: https://github.com/actix/actix-web/pull/2067 -[#2093]: https://github.com/actix/actix-web/pull/2093 +- 2093]: https://github.com/actix/actix-web/pull/2093 [#2094]: https://github.com/actix/actix-web/pull/2094 [#2097]: https://github.com/actix/actix-web/pull/2097 [#2112]: https://github.com/actix/actix-web/pull/2112 @@ -239,8 +239,8 @@ * `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] -[#1981]: https://github.com/actix/actix-web/pull/1981 -[#2010]: https://github.com/actix/actix-web/pull/2010 +- 1981]: https://github.com/actix/actix-web/pull/1981 +- 2010]: https://github.com/actix/actix-web/pull/2010 ## 4.0.0-beta.3 - 2021-02-10 @@ -248,36 +248,36 @@ ## 4.0.0-beta.2 - 2021-02-10 -### Added +- # Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] * Add `services!` macro for helping register multiple services to `App`. [#1933] * Enable registering a vec of services of the same type to `App` [#1933] - +- ### Changed -* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. - Making it simpler and more performant. [#1891] +- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. +- Making it simpler and more performant. [#1891] * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] * `ServiceRequest::from_request` can no longer fail. [#1893] -* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +- 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` provide `&Service` in closure - argument. [#1905] -* `web::block` no longer requires the output is a Result. [#1957] +- in argument [#1905] +- `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure +- argument. [#1905] +- `web::block` no longer requires the output is a Result. [#1957] -### Fixed +- # Fixed * Multiple calls to `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] +- 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 +- 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 @@ -293,26 +293,26 @@ `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration - guide for implications. [#1875] -* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -* MSRV is now 1.46.0. - +- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration +- guide for implications. [#1875] +- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +- MSRV is now 1.46.0. +- ### Fixed -* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] - +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] +- ### Removed * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now - exposed directly by the `middleware` module. +- exposed directly by the `middleware` module. * Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] - +- [#1812]: https://github.com/actix/actix-web/pull/1812 -[#1813]: https://github.com/actix/actix-web/pull/1813 +- 1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 [#1865]: https://github.com/actix/actix-web/pull/1865 [#1875]: https://github.com/actix/actix-web/pull/1875 @@ -325,16 +325,16 @@ [#2529]: https://github.com/actix/actix-web/pull/2529 - +- ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] * Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] * Increase minimum `socket2` version. [#1803] -[#1762]: https://github.com/actix/actix-web/pull/1762 -[#1798]: https://github.com/actix/actix-web/pull/1798 -[#1803]: https://github.com/actix/actix-web/pull/1803 +- 1762]: https://github.com/actix/actix-web/pull/1762 +- 1798]: https://github.com/actix/actix-web/pull/1798 +- 1803]: https://github.com/actix/actix-web/pull/1803 ## 3.3.1 - 2020-11-29 @@ -342,15 +342,15 @@ ## 3.3.0 - 2020-11-25 -### Added +- # Added * Add `Either` extractor helper. [#1788] ### Changed * Upgrade `serde_urlencoded` to `0.7`. [#1773] - +- [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 - +- ## 3.2.0 - 2020-10-30 ### Added @@ -358,17 +358,17 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] -* Expose `on_connect` for access to the connection stream before request is handled. [#1754] - -### Changed -* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -* Print non-configured `Data` type when attempting extraction. [#1743] +- Expose `on_connect` for access to the connection stream before request is handled. [#1754] +- +- # Changed +- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. - -[#1723]: https://github.com/actix/actix-web/pull/1723 -[#1743]: https://github.com/actix/actix-web/pull/1743 -[#1748]: https://github.com/actix/actix-web/pull/1748 +- +- 1723]: https://github.com/actix/actix-web/pull/1723 +- 1743]: https://github.com/actix/actix-web/pull/1743 +- 1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 [#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 @@ -380,13 +380,13 @@ to retain any trailing slashes. [#1695] * Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] - +- ### Fixed -* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 -[#1710]: https://github.com/actix/actix-web/pull/1710 +- 1710]: https://github.com/actix/actix-web/pull/1710 ## 3.0.2 - 2020-09-15 @@ -395,33 +395,33 @@ [#1678]: https://github.com/actix/actix-web/pull/1678 - +- ## 3.0.1 - 2020-09-13 ### Changed * `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 - +- ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 -### Added +- # Added * `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -* Update actix-codec and actix-utils dependencies. [#1634] +- Update actix-codec and actix-utils dependencies. [#1634] * `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] * `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -* `HttpServer::maxconnrate` is renamed to the more expressive - `HttpServer::max_connection_rate`. [#1655] +- `HttpServer::maxconnrate` is renamed to the more expressive +- `HttpServer::max_connection_rate`. [#1655] -[#1639]: https://github.com/actix/actix-web/pull/1639 -[#1641]: https://github.com/actix/actix-web/pull/1641 +- 1639]: https://github.com/actix/actix-web/pull/1639 +- 1641]: https://github.com/actix/actix-web/pull/1641 [#1634]: https://github.com/actix/actix-web/pull/1634 [#1655]: https://github.com/actix/actix-web/pull/1655 @@ -431,22 +431,22 @@ ## 3.0.0-beta.2 - 2020-08-17 -### Changed +- # Changed * `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] * `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -* Re-export all error types from `awc`. [#1621] +- Re-export all error types from `awc`. [#1621] * MSRV is now 1.42.0. - +- ### Fixed -* Memory leak of app data in pooled requests. [#1609] - +- Memory leak of app data in pooled requests. [#1609] +- [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 -[#1610]: https://github.com/actix/actix-web/pull/1610 +- 1610]: https://github.com/actix/actix-web/pull/1610 [#1618]: https://github.com/actix/actix-web/pull/1618 [#1621]: https://github.com/actix/actix-web/pull/1621 @@ -457,29 +457,29 @@ * `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. * `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. - -### Changed +- +- # Changed * Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. * MSRV is now 1.41.1 -### Fixed -* `NormalizePath` improved consistency when path needs slashes added _and_ removed. - +- # Fixed +- `NormalizePath` improved consistency when path needs slashes added _and_ removed. +- ## 3.0.0-alpha.3 - 2020-05-21 -### Added +- # Added * Add option to create `Data` from `Arc` [#1509] ### Changed * Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -* Fix audit issue logging by default peer address [#1485] +- Fix audit issue logging by default peer address [#1485] * Bump minimum supported Rust version to 1.40 * Replace deprecated `net2` crate with `socket2` - -[#1485]: https://github.com/actix/actix-web/pull/1485 -[#1509]: https://github.com/actix/actix-web/pull/1509 - +- +- 1485]: https://github.com/actix/actix-web/pull/1485 +- 1509]: https://github.com/actix/actix-web/pull/1509 +- ## [3.0.0-alpha.2] - 2020-05-08 ### Changed @@ -488,10 +488,10 @@ * Implement `std::error::Error` for our custom errors [#1422] * NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] * Remove the `failure` feature and support. - -[#1422]: https://github.com/actix/actix-web/pull/1422 -[#1433]: https://github.com/actix/actix-web/pull/1433 -[#1452]: https://github.com/actix/actix-web/pull/1452 +- +- 1422]: https://github.com/actix/actix-web/pull/1422 +- 1433]: https://github.com/actix/actix-web/pull/1433 +- 1452]: https://github.com/actix/actix-web/pull/1452 [#1486]: https://github.com/actix/actix-web/pull/1486 @@ -503,16 +503,16 @@ * Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed - -* Use `sha-1` crate instead of unmaintained `sha1` crate +- +- Use `sha-1` crate instead of unmaintained `sha1` crate * Skip empty chunks when returning response from a `Stream` [#1308] * Update the `time` dependency to 0.2.7 * Update `actix-tls` dependency to 2.0.0-alpha.1 -* Update `rustls` dependency to 0.17 - -[#1308]: https://github.com/actix/actix-web/pull/1308 - -## [2.0.0] - 2019-12-25 +- Update `rustls` dependency to 0.17 +- +- 1308]: https://github.com/actix/actix-web/pull/1308 +- +- [2.0.0] - 2019-12-25 ### Changed @@ -520,404 +520,405 @@ * Allow to gracefully stop test server via `TestServer::stop()` -* Allow to specify multi-patterns for resources +- Allow to specify multi-patterns for resources -## [2.0.0-rc] - 2019-12-20 +- [2.0.0-rc] - 2019-12-20 -### Changed +- # Changed * Move `BodyEncoding` to `dev` module #1220 * Allow to set `peer_addr` for TestRequest #1074 -* Make web::Data deref to Arc #1214 +- Make web::Data deref to Arc #1214 -* Rename `App::register_data()` to `App::app_data()` +- Rename `App::register_data()` to `App::app_data()` -* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` -### Fixed +- # Fixed -* Fix `AppConfig::secure()` is always false. #1202 +- Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 - +- ### Fixed * Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 -### Added +- # Added * Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 -### Deleted +- # Deleted * Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 -### Changed +- # Changed * Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 - +- ### Changed * Migrated to `std::future` * Remove implementation of `Responder` for `()`. (#1167) - +- ## [1.0.9] - 2019-11-14 - +- ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 - +- ### Added * Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. * Add `middleware::Condition` that conditionally enables another middleware - +- * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. - +- ### Changed - +- * Make UrlEncodedError::Overflow more informative * Use actix-testing for testing utils - +- ## [1.0.7] - 2019-08-29 - +- ### Fixed * Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 - +- ### Added * Re-implement Host predicate (#989) * Form implements Responder, returning a `application/x-www-form-urlencoded` response -* Add `into_inner` to `Data` +- Add `into_inner` to `Data` -* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. - +- ### Changed - +- * `Query` payload made `pub`. Allows user to pattern-match the payload. * Enable `rust-tls` feature for client #1045 -* Update serde_urlencoded to 0.6.1 - -* Update url to 2.1 +- Update serde_urlencoded to 0.6.1 +- Update url to 2.1 +- ## [1.0.5] - 2019-07-18 - +- ### Added * Unix domain sockets (HttpServer::bind_uds) #92 * Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level - +- ### Fixed - +- * Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 - +- ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` * Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. - +- ### Changed - +- * Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 - +- ### Added * Support asynchronous data factories #850 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 - +- ### Changed * Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. - +- ## [1.0.1] - 2019-06-17 - +- ### Added * Add support for PathConfig #903 * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. -### Changed +- # Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -* Disable default feature `secure-cookies`. +- Disable default feature `secure-cookies`. -* Allow to test an app that uses async actors #897 +- Allow to test an app that uses async actors #897 -* Re-apply patch from #637 #894 +- Re-apply patch from #637 #894 -### Fixed +- # Fixed -* HttpRequest::url_for is broken with nested scopes #915 +- HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 - +- ### Added * Add `Scope::configure()` method. * Add `ServiceRequest::set_payload()` method. -* Add `test::TestRequest::set_json()` convenience method to automatically +- Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. - +- * Add macros for head, options, trace, connect and patch http methods - +- ### Changed -* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -* Fix Logger request time format, and use rfc3339. #867 +- Fix Logger request time format, and use rfc3339. #867 * Clear http requests pool on app service drop #860 - +- ## [1.0.0-rc] - 2019-05-18 - +- ### Added * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed - -* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. +- +- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -* Codegen with parameters in the path only resolves the first registered endpoint #841 +- Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 - +- ### Added * Allow to set/override app data on scope level ### Changed -* `App::configure` take an `FnOnce` instead of `Fn` +- `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates -## [1.0.0-beta.3] - 2019-05-04 - +- [1.0.0-beta.3] - 2019-05-04 +- ### Added * Add helper function for executing futures `test::block_fn()` ### Changed -* Extractor configuration could be registered with `App::data()` +- Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 * Route data is unified with app data, `Route::data()` moved to resource - level to `Resource::data()` +- level to `Resource::data()` * CORS handling without headers #702 - +- * Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. -### Fixed +- # Fixed -* Fix `NormalizePath` middleware impl #806 +- Fix `NormalizePath` middleware impl #806 ### Deleted -* `App::data_factory()` is deleted. +- `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 - +- ### Added * Add raw services support via `web::service()` * Add helper functions for reading response body `test::read_body()` -* Add support for `remainder match` (i.e "/path/{tail}*") +- Add support for `remainder match` (i.e "/path/{tail}*") -* Extend `Responder` trait, allow to override status code and headers. +- Extend `Responder` trait, allow to override status code and headers. -* Store visit and login timestamp in the identity cookie #502 +- Store visit and login timestamp in the identity cookie #502 -### Changed +- # Changed -* `.to_async()` handler can return `Responder` type #792 +- `.to_async()` handler can return `Responder` type #792 ### Fixed -* Fix async web::Data factory handling +- Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 - +- ### Added * Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` * Add `.peer_addr()` #744 - +- * Add `NormalizePath` middleware -### Changed +- # Changed -* Rename `RouterConfig` to `ServiceConfig` +- Rename `RouterConfig` to `ServiceConfig` * Rename `test::call_success` to `test::call_service` -* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -* `CookieIdentityPolicy::max_age()` accepts value in seconds +- `CookieIdentityPolicy::max_age()` accepts value in seconds -### Fixed +- # Fixed -* Fixed `TestRequest::app_data()` +- Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 - +- ### Changed * Allow using any service as default service. * Remove generic type for request payload, always use default. -* Removed `Decompress` middleware. Bytes, String, Json, Form extractors +- Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. - +- * Make extractor config type explicit. Add `FromRequest::Config` associated type. - +- ## [1.0.0-alpha.5] - 2019-04-12 - +- ### Added * Added async io `TestBuffer` for testing. ### Deleted -* Removed native-tls support +- Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 - +- ### Added * `App::configure()` allow to offload app configuration to different methods * Added `URLPath` option for logger -* Added `ServiceRequest::app_data()`, returns `Data` +- Added `ServiceRequest::app_data()`, returns `Data` -* Added `ServiceFromRequest::app_data()`, returns `Data` +- Added `ServiceFromRequest::app_data()`, returns `Data` -### Changed +- # Changed -* `FromRequest` trait refactoring +- `FromRequest` trait refactoring * Move multipart support to actix-multipart crate -### Fixed +- # Fixed -* Fix body propagation in Response::from_error. #760 +- Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 - +- ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -* Removed `Deref` impls +- Removed `Deref` impls -### Removed +- # Removed -* Removed unused `actix_web::web::md()` +- Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 - +- ### Added * Rustls support ### Changed -* Use forked cookie +- Use forked cookie * Multipart::Field renamed to MultipartField -## [1.0.0-alpha.1] - 2019-03-28 +- [1.0.0-alpha.1] - 2019-03-28 -### Changed +- # Changed * Complete architecture re-design. * Return 405 response if no matching route found within resource #538 +- - diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index d6b39e28f..ef8eba0fc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -* No significant changes since `0.6.0-beta.9`. +- No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -* Add `NamedFile::open_async`. [#2408] -* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -* Add `impl Clone` for `FilesService`. [#2408] +- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +- Add `NamedFile::open_async`. [#2408] +- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +- Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -* Added `Files::path_filter()`. [#2274] -* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +- Added `Files::path_filter()`. [#2274] +- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -* Replace `v_htmlescape` with `askama_escape`. [#1953] +- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +- Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -* `HttpRange::parse` now has its own error type. -* Update `bytes` to `1.0`. [#1813] +- `HttpRange::parse` now has its own error type. +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -* Optionally support hidden files/directories. [#1811] +- Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -* Clarify order of parameters in `Files::new` and improve docs. +- Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -* No significant changes from 0.3.0-beta.1. +- No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -* Update `v_htmlescape` to 0.10 -* Update `actix-web` and `actix-http` dependencies to beta.1 +- Update `v_htmlescape` to 0.10 +- Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -* Update `actix-web` and `actix-http` dependencies to alpha -* Fix some typos in the docs -* Bump minimum supported Rust version to 1.40 -* Support sending Content-Length when Content-Range is specified [#1384] +- Update `actix-web` and `actix-http` dependencies to alpha +- Fix some typos in the docs +- Bump minimum supported Rust version to 1.40 +- Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -* Use the same format for file URLs regardless of platforms +- Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -* Fix BodyEncoding trait import #1220 +- Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.1.7 - 2019-11-06 -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -* Add option to redirect to a slash-ended path `Files` #1132 +- Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -* Bump up `mime_guess` crate version to 2.0.1 -* Bump up `percent-encoding` crate version to 2.1 -* Allow user defined request guards for `Files` #1113 +- Bump up `mime_guess` crate version to 2.0.1 +- Bump up `percent-encoding` crate version to 2.1 +- Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -* Allow to disable `Content-Disposition` header #686 +- Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -* Do not set `Content-Length` header, let actix-http set it #930 +- Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -* Content-Length is 0 for NamedFile HEAD request #914 -* Fix ring dependency from actix-web default features for #741 +- Content-Length is 0 for NamedFile HEAD request #914 +- Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -* Static files are incorrectly served as both chunked and with length #812 +- Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -* Update actix-web to beta.4 +- Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -* Update actix-web to beta.1 +- Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Update actix-web to alpha6 +- Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -* Update actix-web to alpha4 +- Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -* Add default handler support +- Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 156012168..4e86e20e8 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -* No significant changes since `3.0.0-beta.8`. +- No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] -* Minimum supported Rust version (MSRV) is now 1.52. +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -* Added `TestServer::client_headers` method. [#2097] +- Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -* Update `bytes` to `1.0`. [#1813] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -* Add ability to set address for `TestServer`. [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Add ability to set address for `TestServer`. [#1645] +- Upgrade `base64` to `0.13`. +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -* Update actix-codec and actix-utils dependencies. +- Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -* Update the `time` dependency to 0.2.7 -* Update `actix-connect` dependency to 2.0.0-alpha.2 -* Make `test_server` `async` fn. -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` -* Update `base64` dependency to 0.12 -* Update `env_logger` dependency to 0.7 +- Update the `time` dependency to 0.2.7 +- Update `actix-connect` dependency to 2.0.0-alpha.2 +- Make `test_server` `async` fn. +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` +- Update `base64` dependency to 0.12 +- Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -* Replaced `TestServer::start()` with `test_server()` +- Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.2.5 - 2019-09-17 -* Update serde_urlencoded to "0.6.1" -* Increase TestServerRuntime timeouts from 500ms to 3000ms -* Do not override current `System` +- Update serde_urlencoded to "0.6.1" +- Increase TestServerRuntime timeouts from 500ms to 3000ms +- Do not override current `System` ## 0.2.4 - 2019-07-18 -* Update actix-server to 0.6 +- Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -* Add `delete`, `options`, `patch` methods to `TestServerRunner` +- Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -* Add .put() and .sput() methods +- Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -* Add license files +- Add license files ## 0.2.0 - 2019-05-12 -* Update awc and actix-http deps +- Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -* Always make new connection for http client +- Always make new connection for http client ## 0.1.0 - 2019-04-16 -* No changes +- No changes ## 0.1.0-alpha.3 - 2019-04-02 -* Request functions accept path #743 +- Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -* Added TestServerRuntime::load_body() method -* Update actix-http and awc libraries +- Added TestServerRuntime::load_body() method +- Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ad98d132a..3b45e934f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -* `Response::map_into_boxed_body`. [#2468] -* `body::EitherBody` enum. [#2468] -* `body::None` struct. [#2468] -* Impl `MessageBody` for `bytestring::ByteString`. [#2468] -* `impl Clone for ws::HandshakeError`. [#2468] -* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -* `impl Default ` for `ws::Codec`. [#1920] -* `header::QualityItem::{max, min}`. [#2486] -* `header::Quality::{MAX, MIN}`. [#2486] -* `impl Display` for `header::Quality`. [#2486] -* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -* `Request::take_conn_data()`. [#2491] -* `Request::take_req_data()`. [#2487] -* `impl Clone` for `RequestHead`. [#2487] -* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +- `Response::map_into_boxed_body`. [#2468] +- `body::EitherBody` enum. [#2468] +- `body::None` struct. [#2468] +- Impl `MessageBody` for `bytestring::ByteString`. [#2468] +- `impl Clone for ws::HandshakeError`. [#2468] +- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +- `impl Default ` for `ws::Codec`. [#1920] +- `header::QualityItem::{max, min}`. [#2486] +- `header::Quality::{MAX, MIN}`. [#2486] +- `impl Display` for `header::Quality`. [#2486] +- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +- `Request::take_conn_data()`. [#2491] +- `Request::take_req_data()`. [#2487] +- `impl Clone` for `RequestHead`. [#2487] +- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -* Rename `body::BoxBody::{from_body => new}`. [#2468] -* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -* Error types using in service builders now require `Into>`. [#2468] -* `From` implementations on error types now return a `Response`. [#2468] -* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -* `ResponseBuilder::finish()` now returns `Response>`. [#2468] +- Rename `body::BoxBody::{from_body => new}`. [#2468] +- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +- Error types using in service builders now require `Into>`. [#2468] +- `From` implementations on error types now return a `Response`. [#2468] +- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +- `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -* `ResponseBuilder::streaming`. [#2468] -* `impl Future` for `ResponseBuilder`. [#2468] -* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -* `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -* `impl TryFrom` for `header::Quality`. [#2486] -* `http` module. Most everything it contained is exported at the crate root. [#2488] +- `ResponseBuilder::streaming`. [#2468] +- `impl Future` for `ResponseBuilder`. [#2468] +- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +- `impl Copy` for `ws::Codec`. [#1920] +- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +- `impl TryFrom` for `header::Quality`. [#2486] +- `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -* Expose `header::map` module. [#2467] -* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +- Expose `header::map` module. [#2467] +- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -* `body::AnyBody::empty` for quickly creating an empty body. [#2446] -* `body::AnyBody::none` for quickly creating a "none" body. [#2456] -* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +- `body::AnyBody::empty` for quickly creating an empty body. [#2446] +- `body::AnyBody::none` for quickly creating a "none" body. [#2456] +- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -* Rename `body::AnyBody::{Message => Body}`. [#2446] -* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -* `Encoder::response` now returns `AnyBody>`. [#2448] +- Rename `body::AnyBody::{Message => Body}`. [#2446] +- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +- `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -* `EncoderError::Boxed`; it is no longer required. [#2446] -* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +- `EncoderError::Boxed`; it is no longer required. [#2446] +- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -* `client` module. [#2425] -* `trust-dns` feature. [#2425] +- `client` module. [#2425] +- `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -* Minimum supported Rust version (MSRV) is now 1.51. +- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -* Remove `Into` bound on `Encoder` body types. [#2375] -* Fix quality parse error in Accept-Encoding header. [#2344] +- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +- Remove `Into` bound on `Encoder` body types. [#2375] +- Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] ### Removed -* `downcast` and `downcast_get_type_id` macros. [#2291] +- `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -* Alias `body::Body` as `body::AnyBody`. [#2215] -* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -* `Response::into_body` that consumes response and returns body type. [#2201] -* `impl Default` for `Response`. [#2201] -* Add zstd support for `ContentEncoding`. [#2244] +- Alias `body::Body` as `body::AnyBody`. [#2215] +- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +- `Response::into_body` that consumes response and returns body type. [#2201] +- `impl Default` for `Response`. [#2201] +- Add zstd support for `ContentEncoding`. [#2244] ### Changed -* The `MessageBody` trait now has an associated `Error` type. [#2183] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -* `header` mod is now public. [#2171] -* `uri` mod is now public. [#2171] -* Update `language-tags` to `0.3`. -* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -* `ResponseBuilder::message_body` now returns a `Result`. [#2201] -* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- The `MessageBody` trait now has an associated `Error` type. [#2183] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +- `header` mod is now public. [#2171] +- `uri` mod is now public. [#2171] +- Update `language-tags` to `0.3`. +- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +- `ResponseBuilder::message_body` now returns a `Result`. [#2201] +- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -* Down-casting for `MessageBody` types. [#2183] -* `error::Result` alias. [#2201] -* Error field from `Response` and `Response::error`. [#2205] -* `impl Future` for `Response`. [#2201] -* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -* `InternalError` and all the error types it constructed. [#2215] -* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +- Down-casting for `MessageBody` types. [#2183] +- `error::Result` alias. [#2201] +- Error field from `Response` and `Response::error`. [#2205] +- `impl Future` for `Response`. [#2201] +- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +- `InternalError` and all the error types it constructed. [#2215] +- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -* `impl MessageBody for Pin>`. [#2152] -* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +- `impl MessageBody for Pin>`. [#2152] +- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -* The type parameter of `Response` no longer has a default. [#2152] -* The `Message` variant of `body::Body` is now `Pin>`. [#2152] -* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -* Error enum types are marked `#[non_exhaustive]`. [#2161] +- The type parameter of `Response` no longer has a default. [#2152] +- The `Message` variant of `body::Body` is now `Pin>`. [#2152] +- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +- Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -* `cookies` feature flag. [#2065] -* Top-level `cookies` mod (re-export). [#2065] -* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -* `impl ResponseError for CookieParseError`. [#2065] -* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -* `ResponseBuilder::json`. [#2148] -* `ResponseBuilder::{set_header, header}`. [#2148] -* `impl From for Body`. [#2148] -* `Response::build_from`. [#2159] -* Most of the status code builders on `Response`. [#2159] +- `cookies` feature flag. [#2065] +- Top-level `cookies` mod (re-export). [#2065] +- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +- `impl ResponseError for CookieParseError`. [#2065] +- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +- `ResponseBuilder::json`. [#2148] +- `ResponseBuilder::{set_header, header}`. [#2148] +- `impl From for Body`. [#2148] +- `Response::build_from`. [#2159] +- Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -* `client::ConnectionIo` trait alias [#2081] +- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] ### Changed -* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common typed HTTP headers were moved to actix-web. [2094] -* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +- Common typed HTTP headers were moved to actix-web. [2094] +- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -* Feature `cookies` is now optional and disabled by default. [#1981] -* `ws::hash_key` now returns array. [#2035] -* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +- Feature `cookies` is now optional and disabled by default. [#1981] +- `ws::hash_key` now returns array. [#2035] +- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `TryIntoHeaderPair` 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] -* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -* `HeaderMap::drain` as an efficient draining iterator. [#1964] -* Implement `IntoIterator` for owned `HeaderMap`. [#1964] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `TryIntoHeaderPair` 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] +- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +- `HeaderMap::drain` as an efficient draining iterator. [#1964] +- Implement `IntoIterator` for owned `HeaderMap`. [#1964] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -* `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()`. [#1903] -* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +- `Extensions::insert` returns Option of replaced item. [#1904] +- Remove `HttpResponseBuilder::json2()`. [#1903] +- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -* `HeaderMap::insert` now returns iterator of removed values. [#1964] -* `HeaderMap::remove` now returns iterator of removed values. [#1964] +- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +- `HeaderMap::insert` now returns iterator of removed values. [#1964] +- `HeaderMap::remove` now returns iterator of removed values. [#1964] ### 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] -* `actors` optional feature. [#1969] -* `ResponseError` impl for `actix::MailboxError`. [#1969] +- `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] +- `actors` optional feature. [#1969] +- `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -* Vastly improve docs and add examples for `HeaderMap`. [#1964] +- Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `bytes` to `1.0`. [#1813] -* Update `h2` to `0.3`. [#1813] -* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `bytes` to `1.0`. [#1813] +- Update `h2` to `0.3`. [#1813] +- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -* Deprecated `on_connect` methods have been removed. Prefer the new +- Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -* HttpResponse builders for 1xx status codes. [#1768] -* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +- HttpResponse builders for 1xx status codes. [#1768] +- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Upgrade `pin-project` to `1.0`. [#1733] -* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Upgrade `pin-project` to `1.0`. [#1733] +- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -* No significant changes from `2.0.0-beta.4`. +- No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec and actix-utils dependencies. -* Update actix-connect and actix-tls dependencies. +- Update actix-codec and actix-utils dependencies. +- Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -* Potential UB in h1 decoder using uninitialized memory. [#1614] +- Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -* Fix illegal chunked encoding. [#1615] +- Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -* Migrate cookie handling to `cookie` crate. [#1558] -* Update `sha-1` to 0.9. [#1586] -* Fix leak in client pool. [#1580] -* MSRV is now 1.41.1. +- Migrate cookie handling to `cookie` crate. [#1558] +- Update `sha-1` to 0.9. [#1586] +- Fix leak in client pool. [#1580] +- MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -* Bump minimum supported Rust version to 1.40 -* content_length function is removed, and you can set Content-Length by calling +- Bump minimum supported Rust version to 1.40 +- content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Update `base64` dependency to 0.12 +- Update `base64` dependency to 0.12 ### Fixed -* Support parsing of `SameSite=None` [#1503] +- Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -* Correct spelling of ConnectError::Unresolved [#1487] -* Fix a mistake in the encoding of websocket continuation messages wherein +- Correct spelling of ConnectError::Unresolved [#1487] +- Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Remove `failure` support for `ResponseError` since that crate +- Implement `std::error::Error` for our custom errors [#1422] +- Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -* client::Connector accepts initial_window_size and initial_connection_window_size +- client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -* Update the `time` dependency to 0.2.7. -* Moved actors messages support from actix crate, enabled with feature `actors`. -* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +- Update the `time` dependency to 0.2.7. +- Moved actors messages support from actix crate, enabled with feature `actors`. +- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -* MessageBody is not implemented for &'static [u8] anymore. +- MessageBody is not implemented for &'static [u8] anymore. ### Fixed -* Allow `SameSite=None` cookies to be sent in a response. +- Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -* Poll upgrade service's readiness from HTTP service handlers -* Replace brotli with brotli2 #1224 +- Poll upgrade service's readiness from HTTP service handlers +- Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -* Add websockets continuation frame support +- Add websockets continuation frame support ### Changed -* Replace `flate2-xxx` features with `compress` +- Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -* Check `Upgrade` service readiness before calling it -* Fix buffer remaining capacity calculation +- Check `Upgrade` service readiness before calling it +- Fix buffer remaining capacity calculation ### Changed -* Websockets: Ping and Pong should have binary data #1049 +- Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -* Add impl ResponseBuilder for Error +- Add impl ResponseBuilder for Error ### Changed -* Use rust based brotli compression library +- Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -* Migrate to tokio 0.2 -* Migrate to `std::future` +- Migrate to tokio 0.2 +- Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` +- Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -* h2 will use error response #1080 -* on_connect result isn't added to request extensions for http2 requests #1009 +- h2 will use error response #1080 +- on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -* Update percent-encoding to 2.1 -* Update serde_urlencoded to 0.6.1 +- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +- Update percent-encoding to 2.1 +- Update serde_urlencoded to 0.6.1 ### Fixed -* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -* Add `rustls` support -* Add `Clone` impl for `HeaderMap` +- Add `rustls` support +- Add `Clone` impl for `HeaderMap` ### Fixed -* awc client panic #1016 -* Invalid response with compression middleware enabled, but compression-related features +- awc client panic #1016 +- Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -* Add support for downcasting response errors #986 +- Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -* Replace `ClonableService` with local copy -* Upgrade `rand` dependency version to 0.7 +- Replace `ClonableService` with local copy +- Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate -* Add `Copy` and `Clone` impls for `ws::Codec` +- Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -* Do not compress NoContent (204) responses #918 +- Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -* Debug impl for ResponseBuilder -* From SizedStream and BodyStream for Body +- Debug impl for ResponseBuilder +- From SizedStream and BodyStream for Body ### Changed -* SizedStream uses u64 +- SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -* Parse incoming stream before closing stream on disconnect #868 +- Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -* Handle socket read disconnect +- Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -* Update actix-service to 0.4 -* Expect and upgrade services accept `ServerConfig` config. +- Update actix-service to 0.4 +- Expect and upgrade services accept `ServerConfig` config. ### Deleted -* `OneRequest` service +- `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -* Clean up response extensions in response pool #817 +- Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -* Allow to render h1 request headers in `Camel-Case` +- Allow to render h1 request headers in `Camel-Case` ### Fixed -* Read until eof for http/1.0 responses #771 +- Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -* Fix http client pool management -* Fix http client wait queue management #794 +- Fix http client pool management +- Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -* Fix BorrowMutError panic in client connector #793 +- Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -* Cookie::max_age() accepts value in seconds -* Cookie::max_age_time() accepts value in time::Duration -* Allow to specify server address for client connector +- Cookie::max_age() accepts value in seconds +- Cookie::max_age_time() accepts value in time::Duration +- Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -* `actix_http::encoding` always available -* use trust-dns-resolver 0.11.0 +- `actix_http::encoding` always available +- use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -* Allow to use custom service for upgrade requests -* Added `h1::SendResponse` future. +- Allow to use custom service for upgrade requests +- Added `h1::SendResponse` future. ### Changed -* MessageBody::length() renamed to MessageBody::size() for consistency -* ws handshake verification functions take RequestHead instead of Request +- MessageBody::length() renamed to MessageBody::size() for consistency +- ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -* Allow to use custom `Expect` handler -* Add minimal `std::error::Error` impl for `Error` +- Allow to use custom `Expect` handler +- Add minimal `std::error::Error` impl for `Error` ### Changed -* Export IntoHeaderValue -* Render error and return as response body -* Use thread pool for response body compression +- Export IntoHeaderValue +- Render error and return as response body +- Use thread pool for response body compression ### Deleted -* Removed PayloadBuffer +- Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -* Warn when an unsealed private cookie isn't valid UTF-8 +- Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -* Rust 1.31.0 compatibility -* Preallocate read buffer for h1 codec -* Detect socket disconnection during protocol selection +- Rust 1.31.0 compatibility +- Preallocate read buffer for h1 codec +- Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -* Added ws::Message::Nop, no-op websockets message +- Added ws::Message::Nop, no-op websockets message ### Changed -* Do not use thread pool for decompression if chunk size is smaller than 2048. +- Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 8d9c1640f..e58c3ee24 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -* No significant changes since `0.4.0-beta.9`. +- No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -* Added `MultipartError::NoContentDisposition` variant. [#2451] -* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -* Added `Field::name` method for getting the field name. [#2451] -* `MultipartError` now marks variants with inner errors as the source. [#2451] -* `MultipartError` is now marked as non-exhaustive. [#2451] +- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +- Added `MultipartError::NoContentDisposition` variant. [#2451] +- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +- Added `Field::name` method for getting the field name. [#2451] +- `MultipartError` now marks variants with inner errors as the source. [#2451] +- `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -* Fix multipart consuming payload before header checks. [#1513] -* Update `bytes` to `1.0`. [#1813] +- Fix multipart consuming payload before header checks. [#1513] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.2`. +- No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -* Update `actix-web` to 3.0.0-beta.1 +- Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -* Update `actix-web` to 3.0.0-alpha.3 -* Bump minimum supported Rust version to 1.40 -* Minimize `futures` dependencies -* Remove the unused `time` dependency -* Fix missing `std::error::Error` implement for `MultipartError`. +- Update `actix-web` to 3.0.0-alpha.3 +- Bump minimum supported Rust version to 1.40 +- Minimize `futures` dependencies +- Remove the unused `time` dependency +- Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -* Release +- Release ## [0.2.0-alpha.4] - 2019-12-xx -* Multipart handling now handles Pending during read of boundary #1205 +- Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -* Migrate to `std::future` +- Migrate to `std::future` ## [0.1.4] - 2019-09-12 -* Multipart handling now parses requests which do not end in CRLF #1038 +- Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -* Fix ring dependency from actix-web default features for #741. +- Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -* Fix boundary parsing #876 +- Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -* Fix disconnect handling #834 +- Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -* Release +- Release ## [0.1.0-beta.4] - 2019-05-12 -* Handle cancellation of uploads #736 +- Handle cancellation of uploads #736 -* Upgrade to actix-web 1.0.0-beta.4 +- Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -* Do not support nested multipart +- Do not support nested multipart -* Split multipart support to separate crate +- Split multipart support to separate crate -* Optimize multipart handling #634, #769 +- Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index d0ed55c88..0a6a56359 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -* Introduce `ResourceDef::join`. [#380] -* Disallow prefix routes with tail segments. [#379] -* Enforce path separators on dynamic prefixes. [#378] -* Improve malformed path error message. [#384] -* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -* Prefix segments with trailing slashes define a trailing empty segment. [#2355] -* Support multi-pattern prefixes and joins. [#2356] -* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -* Support `build_resource_path` on multi-pattern resources. [#2356] -* Minimum supported Rust version (MSRV) is now 1.51. +- Introduce `ResourceDef::join`. [#380] +- Disallow prefix routes with tail segments. [#379] +- Enforce path separators on dynamic prefixes. [#378] +- Improve malformed path error message. [#384] +- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +- Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- Support multi-pattern prefixes and joins. [#2356] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- Support `build_resource_path` on multi-pattern resources. [#2356] +- Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -* Fix `ResourceDef` `PartialEq` implementation. [#373] -* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -* Rename `Router::{*_checked => *_fn}`. [#373] -* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +- Fix `ResourceDef` `PartialEq` implementation. [#373] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +- Rename `Router::{*_checked => *_fn}`. [#373] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -* Path tail patterns now match new lines (`\n`) in request URL. [#360] -* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +- Path tail patterns now match new lines (`\n`) in request URL. [#360] +- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -* Add `Router::recognize_checked` [#247] +- Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -* Use `bytestring` version range compatible with Bytes v1.0. [#246] +- Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -* Fix `from_hex()` method +- Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -* Add `ResourceDef::resource_path_named()` path generation method +- Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -* Add impl `IntoPattern` for `&String` +- Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -* Use `IntoPattern` for `RouterBuilder::path()` +- Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -* Add `IntoPattern` trait -* Add multi-pattern resources +- Add `IntoPattern` trait +- Add multi-pattern resources ## 0.2.0 - 2019-12-07 -* Update http to 0.2 -* Update regex to 1.3 -* Use bytestring instead of string +- Update http to 0.2 +- Update regex to 1.3 +- Use bytestring instead of string ## 0.1.5 - 2019-05-15 -* Remove debug prints +- Remove debug prints ## 0.1.4 - 2019-05-15 -* Fix checked resource match +- Fix checked resource match ## 0.1.3 - 2019-04-22 -* Added support for `remainder match` (i.e "/path/{tail}*") +- Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -* Export `Quoter` type -* Allow to reset `Path` instance +- Export `Quoter` type +- Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -* Get dynamic segment by name instead of iterator. +- Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -* Initial release +- Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index ef78ac54a..e3deeb3f4 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -* Re-export `actix_http::body::to_bytes`. [#2518] -* Update `actix_web::test` re-exports. [#2518] +- Re-export `actix_http::body::to_bytes`. [#2518] +- Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -* No significant changes since `0.1.0-beta.7`. +- No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -* No significant changes from `0.1.0-beta.5`. +- No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -* No significant changes from `0.1.0-beta.2`. +- No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -* No significant changes from `0.1.0-beta.1`. +- No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -* Move integration testing structs from `actix-web`. [#2112] +- Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index d3078499c..6abfe2c61 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -* Minimum supported Rust version (MSRV) is now 1.52. +- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +- Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -* Update `actix` to `0.12`. [#2277] +- Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -* Update `pin-project` to `1.0`. -* Update `bytes` to `1.0`. [#1813] -* `WebsocketContext::text` now takes an `Into`. [#1864] +- Update `pin-project` to `1.0`. +- Update `bytes` to `1.0`. [#1813] +- `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.2`. +- No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -* Update `actix-web` & `actix-http` dependencies to beta.1 -* Bump minimum supported Rust version to 1.40 +- Update `actix-web` & `actix-http` dependencies to beta.1 +- Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -* Update the actix-web dependency to 3.0.0-alpha.1 -* Update the actix dependency to 0.10.0-alpha.2 -* Update the actix-http dependency to 2.0.0-alpha.3 +- Update the actix-web dependency to 3.0.0-alpha.1 +- Update the actix dependency to 0.10.0-alpha.2 +- Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -* Release +- Release ## [2.0.0-alpha.1] - 2019-12-15 -* Migrate to actix-web 2.0.0 +- Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -* Allow comma-separated websocket subprotocols without spaces (#1172) +- Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -* Update actix-web and actix-http dependencies +- Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -* Add `ws::start_with_addr()`, returning the address of the created actor, along +- Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -* Add support for specifying protocols on websocket handshake #835 +- Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -* Allow to use custom ws codec with `WebsocketContext` #925 +- Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 309274563..0d881d303 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -* No significant changes since `0.5.0-beta.5`. +- No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -* Improve error recovery potential when macro input is invalid. [#2410] -* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -* Minimum supported Rust version (MSRV) is now 1.52. +- Improve error recovery potential when macro input is invalid. [#2410] +- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -* In routing macros, paths are now validated at compile time. [#2350] -* Minimum supported Rust version (MSRV) is now 1.51. +- In routing macros, paths are now validated at compile time. [#2350] +- Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -* Preserve doc comments when using route macros. [#2022] -* Add `name` attribute to `route` macro. [#1934] +- Preserve doc comments when using route macros. [#2022] +- Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -* Use new call signature for `System::new`. +- Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -* Added compile success and failure testing. [#1677] -* Add `route` macro for supporting multiple HTTP methods guards. [#1674] +- Added compile success and failure testing. [#1677] +- Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.1`. +- No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -* Add main entry-point macro that uses re-exported runtime. [#1559] +- Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -* Add resource middleware on actix-web-codegen [#1467] +- Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -* Allow the handler function to be named as `config` [#1290] +- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +- Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -* Generate code for actix-web 2.0 +- Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -* Bump up `syn` & `quote` to 1.0 -* Provide better error message +- Bump up `syn` & `quote` to 1.0 +- Provide better error message ## 0.1.2 - 2019-06-04 -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -* Add syn "extra-traits" feature +- Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -* Release +- Release ## 0.1.0-beta.1 - 2019-04-20 -* Gen code for actix-web 1.0.0-beta.1 +- Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Gen code for actix-web 1.0.0-alpha.6 +- Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7b822930c..b5144b7a2 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -* Rename `Connector::{ssl => openssl}`. [#2503] -* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Rename `Connector::{ssl => openssl}`. [#2503] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -* No significant changes since `3.0.0-beta.12`. +- No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -* No significant changes from `3.0.0-beta.10`. +- No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -* No significant changes from `3.0.0-beta.9`. +- No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -* Updated rustls to v0.20. [#2414] +- Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -* Send headers within the redirect requests. [#2310] +- Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -* No significant changes since 3.0.0-beta.5. +- No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -* Fix http/https encoding when enabling `compress` feature. [#2116] -* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +- Feature `cookies` is now optional and enabled by default. [#1981] +- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -* `ClientBuilder::default` function [#2008] +- `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `ClientRequest::insert_header` method which allows using typed headers. [#1869] -* `ClientRequest::append_header` method which allows using typed headers. [#1869] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `ClientRequest::insert_header` method which allows using typed headers. [#1869] +- `ClientRequest::append_header` method which allows using typed headers. [#1869] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +- 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] +- `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 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -* Update `rand` to `0.8` -* Update `bytes` to `1.0`. [#1813] -* Update `rust-tls` to `0.19`. [#1813] +- Update `rand` to `0.8` +- Update `bytes` to `1.0`. [#1813] +- Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -* `Client::build` was renamed to `Client::builder`. +- `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec & actix-tls dependencies. +- Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.2 +- Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.1 +- Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Bump minimum supported Rust version to 1.40 -* Update `base64` dependency to 0.12 +- Implement `std::error::Error` for our custom errors [#1422] +- Bump minimum supported Rust version to 1.40 +- Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -* Update `actix-http` dependency to 2.0.0-alpha.2 -* Update `rustls` dependency to 0.17 -* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -* ClientBuilder allowing to set max_http_version to limit HTTP version to be used +- Update `actix-http` dependency to 2.0.0-alpha.2 +- Update `rustls` dependency to 0.17 +- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +- ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -* Fix compilation with default features off +- Fix compilation with default features off ## [1.0.0] - 2019-12-13 -* Release +- Release ## [1.0.0-alpha.3] -* Migrate to `std::future` +- Migrate to `std::future` ## [0.2.8] - 2019-11-06 -* Add support for setting query from Serialize type for client request. +- Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -* Remaining getter methods for `ClientRequest`'s private `head` field #1101 +- Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -* Export frozen request related types. +- Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -* Add `FrozenClientRequest` to support retries for sending HTTP requests +- Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -* Ensure that the `Host` header is set when initiating a WebSocket client connection. +- Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -* Update percent-encoding to "2.1" +- Update percent-encoding to "2.1" -* Update serde_urlencoded to "0.6.1" +- Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -* Add `rustls` support +- Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -* Always append a colon after username in basic auth +- Always append a colon after username in basic auth -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -* Add license files +- Add license files ## [0.2.0] - 2019-05-12 ### Added -* Allow to send headers in `Camel-Case` form. +- Allow to send headers in `Camel-Case` form. ### Changed -* Upgrade actix-http dependency. +- Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -* Allow to specify server address for http and ws requests. +- Allow to specify server address for http and ws requests. ### Changed -* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -* No changes +- No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -* Do not set default headers for websocket request +- Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -* Do not set any default headers +- Do not set any default headers ### Added -* Add Debug impl for BoxedSocket +- Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -* Update actix-http dependency +- Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -* Export `MessageBody` type +- Export `MessageBody` type -* `ClientResponse::json()` - Loads and parse `application/json` encoded body +- `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -* `ClientRequest::json()` accepts reference instead of object. +- `ClientRequest::json()` accepts reference instead of object. -* `ClientResponse::body()` does not consume response object. +- `ClientResponse::body()` does not consume response object. -* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -* Per request and session wide request timeout. +- Per request and session wide request timeout. -* Session wide headers. +- Session wide headers. -* Session wide basic and bearer auth. +- Session wide basic and bearer auth. -* Re-export `actix_http::client::Connector`. +- Re-export `actix_http::client::Connector`. ### Changed -* Allow to override request's uri +- Allow to override request's uri -* Export `ws` sub-module with websockets related types +- Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl From 212c6926f97f439c2fbbe41f6bd10211653540e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:18:44 +0000 Subject: [PATCH 207/861] Revert "use dash hyphenation in changelogs" This reverts commit 1ea619f2a1722206cddf4af0a43715fc8202a06e. --- CHANGES.md | 551 +++++++++++++++++------------------ actix-files/CHANGES.md | 102 +++---- actix-http-test/CHANGES.md | 76 ++--- actix-http/CHANGES.md | 544 +++++++++++++++++----------------- actix-multipart/CHANGES.md | 74 ++--- actix-router/CHANGES.md | 102 +++---- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 ++-- actix-web-codegen/CHANGES.md | 52 ++-- awc/CHANGES.md | 170 +++++------ 10 files changed, 876 insertions(+), 877 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0458958c5..77ab2e218 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,29 +2,29 @@ ## Unreleased - 2021-xx-xx -- + ## 4.0.0-beta.15 - 2021-12-17 ### Added * Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] * Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] * Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] * Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -- Relax body type and error bounds on test utilities. [#2518] -- -- # Removed -- Top-level `EitherExtractError` export. [#2510] -- Conversion implementations for `either` crate. [#2516] +* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +* Relax body type and error bounds on test utilities. [#2518] + +### Removed +* Top-level `EitherExtractError` export. [#2510] +* Conversion implementations for `either` crate. [#2516] * `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] -- 2510]: https://github.com/actix/actix-web/pull/2510 -- 2515]: https://github.com/actix/actix-web/pull/2515 -- 2516]: https://github.com/actix/actix-web/pull/2516 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2515]: https://github.com/actix/actix-web/pull/2515 +[#2516]: https://github.com/actix/actix-web/pull/2516 [#2518]: https://github.com/actix/actix-web/pull/2518 @@ -34,31 +34,31 @@ * `AcceptEncoding` typed header. [#2482] * `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -- `HttpRequest::{req_data,req_data_mut}`. [#2487] -- `ServiceResponse::into_parts`. [#2499] -- -- # Changed -- Rename `Accept::{mime_precedence => ranked}`. [#2480] -- Rename `Accept::{mime_preference => preference}`. [#2480] +* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +* `HttpRequest::{req_data,req_data_mut}`. [#2487] +* `ServiceResponse::into_parts`. [#2499] + +### Changed +* Rename `Accept::{mime_precedence => ranked}`. [#2480] +* Rename `Accept::{mime_preference => preference}`. [#2480] * Un-deprecate `App::data_factory`. [#2484] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -- Remove `B` (body) type parameter on `App`. [#2493] -- Add `B` (body) type parameter on `Scope`. [#2492] -- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] -- -- # Fixed -- Accept wildcard `*` items in `AcceptLanguage`. [#2480] -- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +* Remove `B` (body) type parameter on `App`. [#2493] +* Add `B` (body) type parameter on `Scope`. [#2492] +* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] + +### Fixed +* Accept wildcard `*` items in `AcceptLanguage`. [#2480] +* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482] -- # Removed -- `ConnectionInfo::get`. [#2487] -- +### Removed +* `ConnectionInfo::get`. [#2487] + [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 -- 2480]: https://github.com/actix/actix-web/pull/2480 +[#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 [#2485]: https://github.com/actix/actix-web/pull/2485 @@ -75,20 +75,20 @@ [#2474]: https://github.com/actix/actix-web/pull/2474 -- + ## 4.0.0-beta.12 - 2021-11-22 ### Changed * Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed * Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] -- + ### Removed * `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] -- + [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 -- + ## 4.0.0-beta.11 - 2021-11-15 ### Added @@ -96,11 +96,11 @@ ### Changed * `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -- Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 -- 2442]: https://github.com/actix/actix-web/pull/2442 -- +[#2442]: https://github.com/actix/actix-web/pull/2442 + ## 4.0.0-beta.10 - 2021-10-20 ### Added @@ -108,18 +108,18 @@ * `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -- Associated type `FromRequest::Config` was removed. [#2233] -- Inner field made private on `web::Payload`. [#2384] +* Associated type `FromRequest::Config` was removed. [#2233] +* Inner field made private on `web::Payload`. [#2384] * `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] * Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. -- -- # Removed -- Useless `ServiceResponse::checked_expr` method. [#2401] -- +* Minimum supported Rust version (MSRV) is now 1.52. + +### Removed +* Useless `ServiceResponse::checked_expr` method. [#2401] + [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 -- 2384]: https://github.com/actix/actix-web/pull/2384 +[#2384]: https://github.com/actix/actix-web/pull/2384 [#2401]: https://github.com/actix/actix-web/pull/2401 [#2403]: https://github.com/actix/actix-web/pull/2403 [#2409]: https://github.com/actix/actix-web/pull/2409 @@ -132,17 +132,17 @@ ### Changed * Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -- Move `BaseHttpResponse` to `dev::Response`. [#2379] +* Move `BaseHttpResponse` to `dev::Response`. [#2379] * Enable `TestRequest::param` to accept more than just static strings. [#2172] * Minimum supported Rust version (MSRV) is now 1.51. -- -- # Fixed -- Fix quality parse error in Accept-Encoding header. [#2344] -- Re-export correct type at `web::HttpResponse`. [#2379] + +### Fixed +* Fix quality parse error in Accept-Encoding header. [#2344] +* Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 -- 2325]: https://github.com/actix/actix-web/pull/2325 -- 2344]: https://github.com/actix/actix-web/pull/2344 +[#2325]: https://github.com/actix/actix-web/pull/2325 +[#2344]: https://github.com/actix/actix-web/pull/2344 [#2379]: https://github.com/actix/actix-web/pull/2379 @@ -152,18 +152,18 @@ * Add extractors for `Uri` and `Method`. [#2263] * Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] * Add `Route::service` for using hand-written services as handlers. [#2262] -- -- # Changed -- Change compression algorithm features flags. [#2250] -- Deprecate `App::data` and `App::data_factory`. [#2271] + +### Changed +* Change compression algorithm features flags. [#2250] +* Deprecate `App::data` and `App::data_factory`. [#2271] * Smarter extraction of `ConnectionInfo` parts. [#2282] -- # Fixed -- Scope and Resource middleware can access data items set on their own layer. [#2288] -- +### Fixed +* Scope and Resource middleware can access data items set on their own layer. [#2288] + [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 -- 2271]: https://github.com/actix/actix-web/pull/2271 +[#2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 [#2282]: https://github.com/actix/actix-web/pull/2282 @@ -176,23 +176,23 @@ ### Changed * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -- 2162]: (https://github.com/actix/actix-web/pull/2162) +[#2162]: (https://github.com/actix/actix-web/pull/2162) * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] * `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -- Update `language-tags` to `0.3`. +* Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] -- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] -- -- # Removed -- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] -- +* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] + +### Removed +* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] + [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 -- 2253]: https://github.com/actix/actix-web/pull/2253 +[#2253]: https://github.com/actix/actix-web/pull/2253 [#2246]: https://github.com/actix/actix-web/pull/2246 @@ -202,11 +202,11 @@ ### Changed * Most error types are now marked `#[non_exhaustive]`. [#2148] -- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 -- 2148]: https://github.com/actix/actix-web/pull/2148 -- +[#2148]: https://github.com/actix/actix-web/pull/2148 + ## 4.0.0-beta.5 - 2021-04-02 ### Added @@ -214,20 +214,20 @@ * Added `TestServer::client_headers` method. [#2097] ### Fixed -- Double ampersand in Logger format is escaped correctly. [#2067] -- +* Double ampersand in Logger format is escaped correctly. [#2067] + ### Changed * `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed -- instead of skipping. (Only the first error is kept when multiple error occur) [#2093] + instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -- The `client` mod was removed. Clients should now use `awc` directly. +* The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) * Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] -- + [#2067]: https://github.com/actix/actix-web/pull/2067 -- 2093]: https://github.com/actix/actix-web/pull/2093 +[#2093]: https://github.com/actix/actix-web/pull/2093 [#2094]: https://github.com/actix/actix-web/pull/2094 [#2097]: https://github.com/actix/actix-web/pull/2097 [#2112]: https://github.com/actix/actix-web/pull/2112 @@ -239,8 +239,8 @@ * `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] -- 1981]: https://github.com/actix/actix-web/pull/1981 -- 2010]: https://github.com/actix/actix-web/pull/2010 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#2010]: https://github.com/actix/actix-web/pull/2010 ## 4.0.0-beta.3 - 2021-02-10 @@ -248,36 +248,36 @@ ## 4.0.0-beta.2 - 2021-02-10 -- # Added +### Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] * Add `services!` macro for helping register multiple services to `App`. [#1933] * Enable registering a vec of services of the same type to `App` [#1933] -- + ### Changed -- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. -- Making it simpler and more performant. [#1891] +* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. + Making it simpler and more performant. [#1891] * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] * `ServiceRequest::from_request` can no longer fail. [#1893] -- Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* 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` provide `&Service` in closure -- argument. [#1905] -- `web::block` no longer requires the output is a Result. [#1957] + in argument [#1905] +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure + argument. [#1905] +* `web::block` no longer requires the output is a Result. [#1957] -- # Fixed +### Fixed * Multiple calls to `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] +* 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 + 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 @@ -293,26 +293,26 @@ `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration -- guide for implications. [#1875] -- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -- MSRV is now 1.46.0. -- +* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration + guide for implications. [#1875] +* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +* MSRV is now 1.46.0. + ### Fixed -- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] -- +* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + ### Removed * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now -- exposed directly by the `middleware` module. + exposed directly by the `middleware` module. * Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] -- + [#1812]: https://github.com/actix/actix-web/pull/1812 -- 1813]: https://github.com/actix/actix-web/pull/1813 +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 [#1865]: https://github.com/actix/actix-web/pull/1865 [#1875]: https://github.com/actix/actix-web/pull/1875 @@ -325,16 +325,16 @@ [#2529]: https://github.com/actix/actix-web/pull/2529 -- + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] * Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] * Increase minimum `socket2` version. [#1803] -- 1762]: https://github.com/actix/actix-web/pull/1762 -- 1798]: https://github.com/actix/actix-web/pull/1798 -- 1803]: https://github.com/actix/actix-web/pull/1803 +[#1762]: https://github.com/actix/actix-web/pull/1762 +[#1798]: https://github.com/actix/actix-web/pull/1798 +[#1803]: https://github.com/actix/actix-web/pull/1803 ## 3.3.1 - 2020-11-29 @@ -342,15 +342,15 @@ ## 3.3.0 - 2020-11-25 -- # Added +### Added * Add `Either` extractor helper. [#1788] ### Changed * Upgrade `serde_urlencoded` to `0.7`. [#1773] -- + [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 -- + ## 3.2.0 - 2020-10-30 ### Added @@ -358,17 +358,17 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] -- Expose `on_connect` for access to the connection stream before request is handled. [#1754] -- -- # Changed -- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -- Print non-configured `Data` type when attempting extraction. [#1743] +* Expose `on_connect` for access to the connection stream before request is handled. [#1754] + +### Changed +* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +* Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. -- -- 1723]: https://github.com/actix/actix-web/pull/1723 -- 1743]: https://github.com/actix/actix-web/pull/1743 -- 1748]: https://github.com/actix/actix-web/pull/1748 + +[#1723]: https://github.com/actix/actix-web/pull/1723 +[#1743]: https://github.com/actix/actix-web/pull/1743 +[#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 [#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 @@ -380,13 +380,13 @@ to retain any trailing slashes. [#1695] * Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] -- + ### Fixed -- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 -- 1710]: https://github.com/actix/actix-web/pull/1710 +[#1710]: https://github.com/actix/actix-web/pull/1710 ## 3.0.2 - 2020-09-15 @@ -395,33 +395,33 @@ [#1678]: https://github.com/actix/actix-web/pull/1678 -- + ## 3.0.1 - 2020-09-13 ### Changed * `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 -- + ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 -- # Added +### Added * `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -- Update actix-codec and actix-utils dependencies. [#1634] +* Update actix-codec and actix-utils dependencies. [#1634] * `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] * `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -- `HttpServer::maxconnrate` is renamed to the more expressive -- `HttpServer::max_connection_rate`. [#1655] +* `HttpServer::maxconnrate` is renamed to the more expressive + `HttpServer::max_connection_rate`. [#1655] -- 1639]: https://github.com/actix/actix-web/pull/1639 -- 1641]: https://github.com/actix/actix-web/pull/1641 +[#1639]: https://github.com/actix/actix-web/pull/1639 +[#1641]: https://github.com/actix/actix-web/pull/1641 [#1634]: https://github.com/actix/actix-web/pull/1634 [#1655]: https://github.com/actix/actix-web/pull/1655 @@ -431,22 +431,22 @@ ## 3.0.0-beta.2 - 2020-08-17 -- # Changed +### Changed * `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] * `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -- Re-export all error types from `awc`. [#1621] +* Re-export all error types from `awc`. [#1621] * MSRV is now 1.42.0. -- + ### Fixed -- Memory leak of app data in pooled requests. [#1609] -- +* Memory leak of app data in pooled requests. [#1609] + [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 -- 1610]: https://github.com/actix/actix-web/pull/1610 +[#1610]: https://github.com/actix/actix-web/pull/1610 [#1618]: https://github.com/actix/actix-web/pull/1618 [#1621]: https://github.com/actix/actix-web/pull/1621 @@ -457,29 +457,29 @@ * `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. * `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. -- -- # Changed + +### Changed * Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. * MSRV is now 1.41.1 -- # Fixed -- `NormalizePath` improved consistency when path needs slashes added _and_ removed. -- +### Fixed +* `NormalizePath` improved consistency when path needs slashes added _and_ removed. + ## 3.0.0-alpha.3 - 2020-05-21 -- # Added +### Added * Add option to create `Data` from `Arc` [#1509] ### Changed * Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -- Fix audit issue logging by default peer address [#1485] +* Fix audit issue logging by default peer address [#1485] * Bump minimum supported Rust version to 1.40 * Replace deprecated `net2` crate with `socket2` -- -- 1485]: https://github.com/actix/actix-web/pull/1485 -- 1509]: https://github.com/actix/actix-web/pull/1509 -- + +[#1485]: https://github.com/actix/actix-web/pull/1485 +[#1509]: https://github.com/actix/actix-web/pull/1509 + ## [3.0.0-alpha.2] - 2020-05-08 ### Changed @@ -488,10 +488,10 @@ * Implement `std::error::Error` for our custom errors [#1422] * NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] * Remove the `failure` feature and support. -- -- 1422]: https://github.com/actix/actix-web/pull/1422 -- 1433]: https://github.com/actix/actix-web/pull/1433 -- 1452]: https://github.com/actix/actix-web/pull/1452 + +[#1422]: https://github.com/actix/actix-web/pull/1422 +[#1433]: https://github.com/actix/actix-web/pull/1433 +[#1452]: https://github.com/actix/actix-web/pull/1452 [#1486]: https://github.com/actix/actix-web/pull/1486 @@ -503,16 +503,16 @@ * Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed -- -- Use `sha-1` crate instead of unmaintained `sha1` crate + +* Use `sha-1` crate instead of unmaintained `sha1` crate * Skip empty chunks when returning response from a `Stream` [#1308] * Update the `time` dependency to 0.2.7 * Update `actix-tls` dependency to 2.0.0-alpha.1 -- Update `rustls` dependency to 0.17 -- -- 1308]: https://github.com/actix/actix-web/pull/1308 -- -- [2.0.0] - 2019-12-25 +* Update `rustls` dependency to 0.17 + +[#1308]: https://github.com/actix/actix-web/pull/1308 + +## [2.0.0] - 2019-12-25 ### Changed @@ -520,405 +520,404 @@ * Allow to gracefully stop test server via `TestServer::stop()` -- Allow to specify multi-patterns for resources +* Allow to specify multi-patterns for resources -- [2.0.0-rc] - 2019-12-20 +## [2.0.0-rc] - 2019-12-20 -- # Changed +### Changed * Move `BodyEncoding` to `dev` module #1220 * Allow to set `peer_addr` for TestRequest #1074 -- Make web::Data deref to Arc #1214 +* Make web::Data deref to Arc #1214 -- Rename `App::register_data()` to `App::app_data()` +* Rename `App::register_data()` to `App::app_data()` -- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` -- # Fixed +### Fixed -- Fix `AppConfig::secure()` is always false. #1202 +* Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 -- + ### Fixed * Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 -- # Added +### Added * Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 -- # Deleted +### Deleted * Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 -- # Changed +### Changed * Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 -- + ### Changed * Migrated to `std::future` * Remove implementation of `Responder` for `()`. (#1167) -- + ## [1.0.9] - 2019-11-14 -- + ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 -- + ### Added * Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. * Add `middleware::Condition` that conditionally enables another middleware -- + * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. -- + ### Changed -- + * Make UrlEncodedError::Overflow more informative * Use actix-testing for testing utils -- + ## [1.0.7] - 2019-08-29 -- + ### Fixed * Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 -- + ### Added * Re-implement Host predicate (#989) * Form implements Responder, returning a `application/x-www-form-urlencoded` response -- Add `into_inner` to `Data` +* Add `into_inner` to `Data` -- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. -- + ### Changed -- + * `Query` payload made `pub`. Allows user to pattern-match the payload. * Enable `rust-tls` feature for client #1045 -- Update serde_urlencoded to 0.6.1 +* Update serde_urlencoded to 0.6.1 + +* Update url to 2.1 -- Update url to 2.1 -- ## [1.0.5] - 2019-07-18 -- + ### Added * Unix domain sockets (HttpServer::bind_uds) #92 * Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level -- + ### Fixed -- + * Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 -- + ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` * Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. -- + ### Changed -- + * Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 -- + ### Added * Support asynchronous data factories #850 ### Changed -- Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 -- + ### Changed * Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -- + ## [1.0.1] - 2019-06-17 -- + ### Added * Add support for PathConfig #903 * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. -- # Changed +### Changed -- Move cors middleware to `actix-cors` crate. +* Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. -- Disable default feature `secure-cookies`. +* Disable default feature `secure-cookies`. -- Allow to test an app that uses async actors #897 +* Allow to test an app that uses async actors #897 -- Re-apply patch from #637 #894 +* Re-apply patch from #637 #894 -- # Fixed +### Fixed -- HttpRequest::url_for is broken with nested scopes #915 +* HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 -- + ### Added * Add `Scope::configure()` method. * Add `ServiceRequest::set_payload()` method. -- Add `test::TestRequest::set_json()` convenience method to automatically +* Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. -- + * Add macros for head, options, trace, connect and patch http methods -- + ### Changed -- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -- Fix Logger request time format, and use rfc3339. #867 +* Fix Logger request time format, and use rfc3339. #867 * Clear http requests pool on app service drop #860 -- + ## [1.0.0-rc] - 2019-05-18 -- + ### Added * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed -- -- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. + +* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -- Codegen with parameters in the path only resolves the first registered endpoint #841 +* Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 -- + ### Added * Allow to set/override app data on scope level ### Changed -- `App::configure` take an `FnOnce` instead of `Fn` +* `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates -- [1.0.0-beta.3] - 2019-05-04 -- +## [1.0.0-beta.3] - 2019-05-04 + ### Added * Add helper function for executing futures `test::block_fn()` ### Changed -- Extractor configuration could be registered with `App::data()` +* Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 * Route data is unified with app data, `Route::data()` moved to resource -- level to `Resource::data()` + level to `Resource::data()` * CORS handling without headers #702 -- + * Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. -- # Fixed +### Fixed -- Fix `NormalizePath` middleware impl #806 +* Fix `NormalizePath` middleware impl #806 ### Deleted -- `App::data_factory()` is deleted. +* `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 -- + ### Added * Add raw services support via `web::service()` * Add helper functions for reading response body `test::read_body()` -- Add support for `remainder match` (i.e "/path/{tail}*") +* Add support for `remainder match` (i.e "/path/{tail}*") -- Extend `Responder` trait, allow to override status code and headers. +* Extend `Responder` trait, allow to override status code and headers. -- Store visit and login timestamp in the identity cookie #502 +* Store visit and login timestamp in the identity cookie #502 -- # Changed +### Changed -- `.to_async()` handler can return `Responder` type #792 +* `.to_async()` handler can return `Responder` type #792 ### Fixed -- Fix async web::Data factory handling +* Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 -- + ### Added * Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` * Add `.peer_addr()` #744 -- + * Add `NormalizePath` middleware -- # Changed +### Changed -- Rename `RouterConfig` to `ServiceConfig` +* Rename `RouterConfig` to `ServiceConfig` * Rename `test::call_success` to `test::call_service` -- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -- `CookieIdentityPolicy::max_age()` accepts value in seconds +* `CookieIdentityPolicy::max_age()` accepts value in seconds -- # Fixed +### Fixed -- Fixed `TestRequest::app_data()` +* Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 -- + ### Changed * Allow using any service as default service. * Remove generic type for request payload, always use default. -- Removed `Decompress` middleware. Bytes, String, Json, Form extractors +* Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. -- + * Make extractor config type explicit. Add `FromRequest::Config` associated type. -- + ## [1.0.0-alpha.5] - 2019-04-12 -- + ### Added * Added async io `TestBuffer` for testing. ### Deleted -- Removed native-tls support +* Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 -- + ### Added * `App::configure()` allow to offload app configuration to different methods * Added `URLPath` option for logger -- Added `ServiceRequest::app_data()`, returns `Data` +* Added `ServiceRequest::app_data()`, returns `Data` -- Added `ServiceFromRequest::app_data()`, returns `Data` +* Added `ServiceFromRequest::app_data()`, returns `Data` -- # Changed +### Changed -- `FromRequest` trait refactoring +* `FromRequest` trait refactoring * Move multipart support to actix-multipart crate -- # Fixed +### Fixed -- Fix body propagation in Response::from_error. #760 +* Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 -- + ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -- Removed `Deref` impls +* Removed `Deref` impls -- # Removed +### Removed -- Removed unused `actix_web::web::md()` +* Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 -- + ### Added * Rustls support ### Changed -- Use forked cookie +* Use forked cookie * Multipart::Field renamed to MultipartField -- [1.0.0-alpha.1] - 2019-03-28 +## [1.0.0-alpha.1] - 2019-03-28 -- # Changed +### Changed * Complete architecture re-design. * Return 405 response if no matching route found within resource #538 -- - diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ef8eba0fc..d6b39e28f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -- No significant changes since `0.6.0-beta.9`. +* No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -- Add `NamedFile::open_async`. [#2408] -- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -- Add `impl Clone` for `FilesService`. [#2408] +* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +* Add `NamedFile::open_async`. [#2408] +* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +* Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -- Added `Files::path_filter()`. [#2274] -- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +* Added `Files::path_filter()`. [#2274] +* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -- Replace `v_htmlescape` with `askama_escape`. [#1953] +* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +* Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -- `HttpRange::parse` now has its own error type. -- Update `bytes` to `1.0`. [#1813] +* `HttpRange::parse` now has its own error type. +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -- Optionally support hidden files/directories. [#1811] +* Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -- Clarify order of parameters in `Files::new` and improve docs. +* Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -- No significant changes from 0.3.0-beta.1. +* No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -- Update `v_htmlescape` to 0.10 -- Update `actix-web` and `actix-http` dependencies to beta.1 +* Update `v_htmlescape` to 0.10 +* Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -- Update `actix-web` and `actix-http` dependencies to alpha -- Fix some typos in the docs -- Bump minimum supported Rust version to 1.40 -- Support sending Content-Length when Content-Range is specified [#1384] +* Update `actix-web` and `actix-http` dependencies to alpha +* Fix some typos in the docs +* Bump minimum supported Rust version to 1.40 +* Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -- Use the same format for file URLs regardless of platforms +* Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -- Fix BodyEncoding trait import #1220 +* Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -- Migrate to `std::future` +* Migrate to `std::future` ## 0.1.7 - 2019-11-06 -- Add an additional `filename*` param in the `Content-Disposition` header of +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -- Add option to redirect to a slash-ended path `Files` #1132 +* Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -- Bump up `mime_guess` crate version to 2.0.1 -- Bump up `percent-encoding` crate version to 2.1 -- Allow user defined request guards for `Files` #1113 +* Bump up `mime_guess` crate version to 2.0.1 +* Bump up `percent-encoding` crate version to 2.1 +* Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -- Allow to disable `Content-Disposition` header #686 +* Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -- Do not set `Content-Length` header, let actix-http set it #930 +* Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -- Content-Length is 0 for NamedFile HEAD request #914 -- Fix ring dependency from actix-web default features for #741 +* Content-Length is 0 for NamedFile HEAD request #914 +* Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -- Static files are incorrectly served as both chunked and with length #812 +* Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -- Update actix-web to beta.4 +* Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -- Update actix-web to beta.1 +* Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -- Update actix-web to alpha6 +* Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -- Update actix-web to alpha4 +* Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -- Add default handler support +* Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 4e86e20e8..156012168 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -- No significant changes since `3.0.0-beta.8`. +* No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -- Update `actix-server` to `2.0.0-beta.9`. [#2442] -- Minimum supported Rust version (MSRV) is now 1.52. +* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -- Added `TestServer::client_headers` method. [#2097] +* Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -- Update `bytes` to `1.0`. [#1813] +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -- Add ability to set address for `TestServer`. [#1645] -- Upgrade `base64` to `0.13`. -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Add ability to set address for `TestServer`. [#1645] +* Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -- Update actix-codec and actix-utils dependencies. +* Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -- Update the `time` dependency to 0.2.7 -- Update `actix-connect` dependency to 2.0.0-alpha.2 -- Make `test_server` `async` fn. -- Bump minimum supported Rust version to 1.40 -- Replace deprecated `net2` crate with `socket2` -- Update `base64` dependency to 0.12 -- Update `env_logger` dependency to 0.7 +* Update the `time` dependency to 0.2.7 +* Update `actix-connect` dependency to 2.0.0-alpha.2 +* Make `test_server` `async` fn. +* Bump minimum supported Rust version to 1.40 +* Replace deprecated `net2` crate with `socket2` +* Update `base64` dependency to 0.12 +* Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -- Replaced `TestServer::start()` with `test_server()` +* Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -- Migrate to `std::future` +* Migrate to `std::future` ## 0.2.5 - 2019-09-17 -- Update serde_urlencoded to "0.6.1" -- Increase TestServerRuntime timeouts from 500ms to 3000ms -- Do not override current `System` +* Update serde_urlencoded to "0.6.1" +* Increase TestServerRuntime timeouts from 500ms to 3000ms +* Do not override current `System` ## 0.2.4 - 2019-07-18 -- Update actix-server to 0.6 +* Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -- Add `delete`, `options`, `patch` methods to `TestServerRunner` +* Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -- Add .put() and .sput() methods +* Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -- Add license files +* Add license files ## 0.2.0 - 2019-05-12 -- Update awc and actix-http deps +* Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -- Always make new connection for http client +* Always make new connection for http client ## 0.1.0 - 2019-04-16 -- No changes +* No changes ## 0.1.0-alpha.3 - 2019-04-02 -- Request functions accept path #743 +* Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -- Added TestServerRuntime::load_body() method -- Update actix-http and awc libraries +* Added TestServerRuntime::load_body() method +* Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3b45e934f..ad98d132a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -- `Response::map_into_boxed_body`. [#2468] -- `body::EitherBody` enum. [#2468] -- `body::None` struct. [#2468] -- Impl `MessageBody` for `bytestring::ByteString`. [#2468] -- `impl Clone for ws::HandshakeError`. [#2468] -- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -- `impl Default ` for `ws::Codec`. [#1920] -- `header::QualityItem::{max, min}`. [#2486] -- `header::Quality::{MAX, MIN}`. [#2486] -- `impl Display` for `header::Quality`. [#2486] -- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -- `Request::take_conn_data()`. [#2491] -- `Request::take_req_data()`. [#2487] -- `impl Clone` for `RequestHead`. [#2487] -- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +* `Response::map_into_boxed_body`. [#2468] +* `body::EitherBody` enum. [#2468] +* `body::None` struct. [#2468] +* Impl `MessageBody` for `bytestring::ByteString`. [#2468] +* `impl Clone for ws::HandshakeError`. [#2468] +* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +* `impl Default ` for `ws::Codec`. [#1920] +* `header::QualityItem::{max, min}`. [#2486] +* `header::Quality::{MAX, MIN}`. [#2486] +* `impl Display` for `header::Quality`. [#2486] +* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +* `Request::take_conn_data()`. [#2491] +* `Request::take_req_data()`. [#2487] +* `impl Clone` for `RequestHead`. [#2487] +* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -- Rename `body::BoxBody::{from_body => new}`. [#2468] -- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -- Error types using in service builders now require `Into>`. [#2468] -- `From` implementations on error types now return a `Response`. [#2468] -- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -- `ResponseBuilder::finish()` now returns `Response>`. [#2468] +* Rename `body::BoxBody::{from_body => new}`. [#2468] +* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +* Error types using in service builders now require `Into>`. [#2468] +* `From` implementations on error types now return a `Response`. [#2468] +* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +* `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -- `ResponseBuilder::streaming`. [#2468] -- `impl Future` for `ResponseBuilder`. [#2468] -- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -- `impl Copy` for `ws::Codec`. [#1920] -- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -- `impl TryFrom` for `header::Quality`. [#2486] -- `http` module. Most everything it contained is exported at the crate root. [#2488] +* `ResponseBuilder::streaming`. [#2468] +* `impl Future` for `ResponseBuilder`. [#2468] +* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +* `impl Copy` for `ws::Codec`. [#1920] +* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +* `impl TryFrom` for `header::Quality`. [#2486] +* `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -- Expose `header::map` module. [#2467] -- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +* Expose `header::map` module. [#2467] +* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -- `body::AnyBody::empty` for quickly creating an empty body. [#2446] -- `body::AnyBody::none` for quickly creating a "none" body. [#2456] -- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +* `body::AnyBody::empty` for quickly creating an empty body. [#2446] +* `body::AnyBody::none` for quickly creating a "none" body. [#2456] +* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -- Rename `body::AnyBody::{Message => Body}`. [#2446] -- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -- `Encoder::response` now returns `AnyBody>`. [#2448] +* Rename `body::AnyBody::{Message => Body}`. [#2446] +* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +* `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -- `EncoderError::Boxed`; it is no longer required. [#2446] -- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +* `EncoderError::Boxed`; it is no longer required. [#2446] +* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -- Update `actix-server` to `2.0.0-beta.9`. [#2442] +* Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -- `client` module. [#2425] -- `trust-dns` feature. [#2425] +* `client` module. [#2425] +* `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -- Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. +* Updated rustls to v0.20. [#2414] +* Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -- Minimum supported Rust version (MSRV) is now 1.51. +* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +* Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -- Remove `Into` bound on `Encoder` body types. [#2375] -- Fix quality parse error in Accept-Encoding header. [#2344] +* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +* Remove `Into` bound on `Encoder` body types. [#2375] +* Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -- Change compression algorithm features flags. [#2250] +* Change compression algorithm features flags. [#2250] ### Removed -- `downcast` and `downcast_get_type_id` macros. [#2291] +* `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -- Alias `body::Body` as `body::AnyBody`. [#2215] -- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -- `Response::into_body` that consumes response and returns body type. [#2201] -- `impl Default` for `Response`. [#2201] -- Add zstd support for `ContentEncoding`. [#2244] +* Alias `body::Body` as `body::AnyBody`. [#2215] +* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +* `Response::into_body` that consumes response and returns body type. [#2201] +* `impl Default` for `Response`. [#2201] +* Add zstd support for `ContentEncoding`. [#2244] ### Changed -- The `MessageBody` trait now has an associated `Error` type. [#2183] -- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -- `header` mod is now public. [#2171] -- `uri` mod is now public. [#2171] -- Update `language-tags` to `0.3`. -- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -- `ResponseBuilder::message_body` now returns a `Result`. [#2201] -- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +* The `MessageBody` trait now has an associated `Error` type. [#2183] +* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +* `header` mod is now public. [#2171] +* `uri` mod is now public. [#2171] +* Update `language-tags` to `0.3`. +* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +* `ResponseBuilder::message_body` now returns a `Result`. [#2201] +* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -- Down-casting for `MessageBody` types. [#2183] -- `error::Result` alias. [#2201] -- Error field from `Response` and `Response::error`. [#2205] -- `impl Future` for `Response`. [#2201] -- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -- `InternalError` and all the error types it constructed. [#2215] -- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +* Down-casting for `MessageBody` types. [#2183] +* `error::Result` alias. [#2201] +* Error field from `Response` and `Response::error`. [#2205] +* `impl Future` for `Response`. [#2201] +* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +* `InternalError` and all the error types it constructed. [#2215] +* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -- `impl MessageBody for Pin>`. [#2152] -- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +* `impl MessageBody for Pin>`. [#2152] +* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -- The type parameter of `Response` no longer has a default. [#2152] -- The `Message` variant of `body::Body` is now `Pin>`. [#2152] -- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -- Error enum types are marked `#[non_exhaustive]`. [#2161] +* The type parameter of `Response` no longer has a default. [#2152] +* The `Message` variant of `body::Body` is now `Pin>`. [#2152] +* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +* Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -- `cookies` feature flag. [#2065] -- Top-level `cookies` mod (re-export). [#2065] -- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -- `impl ResponseError for CookieParseError`. [#2065] -- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -- `ResponseBuilder::json`. [#2148] -- `ResponseBuilder::{set_header, header}`. [#2148] -- `impl From for Body`. [#2148] -- `Response::build_from`. [#2159] -- Most of the status code builders on `Response`. [#2159] +* `cookies` feature flag. [#2065] +* Top-level `cookies` mod (re-export). [#2065] +* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +* `impl ResponseError for CookieParseError`. [#2065] +* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +* `ResponseBuilder::json`. [#2148] +* `ResponseBuilder::{set_header, header}`. [#2148] +* `impl From for Body`. [#2148] +* `Response::build_from`. [#2159] +* Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -- `client::ConnectionIo` trait alias [#2081] +* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +* `client::ConnectionIo` trait alias [#2081] ### Changed -- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -- Common typed HTTP headers were moved to actix-web. [2094] -- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +* Common typed HTTP headers were moved to actix-web. [2094] +* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -- Feature `cookies` is now optional and disabled by default. [#1981] -- `ws::hash_key` now returns array. [#2035] -- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +* Feature `cookies` is now optional and disabled by default. [#1981] +* `ws::hash_key` now returns array. [#2035] +* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -- No notable changes. +* No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -- `TryIntoHeaderPair` 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] -- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -- `HeaderMap::drain` as an efficient draining iterator. [#1964] -- Implement `IntoIterator` for owned `HeaderMap`. [#1964] -- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +* `TryIntoHeaderPair` 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] +* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +* `HeaderMap::drain` as an efficient draining iterator. [#1964] +* Implement `IntoIterator` for owned `HeaderMap`. [#1964] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -- `Extensions::insert` returns Option of replaced item. [#1904] -- Remove `HttpResponseBuilder::json2()`. [#1903] -- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +* `Extensions::insert` returns Option of replaced item. [#1904] +* Remove `HttpResponseBuilder::json2()`. [#1903] +* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -- `HeaderMap::insert` now returns iterator of removed values. [#1964] -- `HeaderMap::remove` now returns iterator of removed values. [#1964] +* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +* `HeaderMap::insert` now returns iterator of removed values. [#1964] +* `HeaderMap::remove` now returns iterator of removed values. [#1964] ### 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] -- `actors` optional feature. [#1969] -- `ResponseError` impl for `actix::MailboxError`. [#1969] +* `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] +* `actors` optional feature. [#1969] +* `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -- Vastly improve docs and add examples for `HeaderMap`. [#1964] +* Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -- Bumped `rand` to `0.8`. -- Update `bytes` to `1.0`. [#1813] -- Update `h2` to `0.3`. [#1813] -- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. +* Update `bytes` to `1.0`. [#1813] +* Update `h2` to `0.3`. [#1813] +* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -- Deprecated `on_connect` methods have been removed. Prefer the new +* Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -- HttpResponse builders for 1xx status codes. [#1768] -- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +* HttpResponse builders for 1xx status codes. [#1768] +* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -- Upgrade `base64` to `0.13`. [#1744] -- Upgrade `pin-project` to `1.0`. [#1733] -- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +* Upgrade `base64` to `0.13`. [#1744] +* Upgrade `pin-project` to `1.0`. [#1733] +* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -- No significant changes from `2.0.0-beta.4`. +* No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -- Update actix-codec and actix-utils dependencies. -- Update actix-connect and actix-tls dependencies. +* Update actix-codec and actix-utils dependencies. +* Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -- Potential UB in h1 decoder using uninitialized memory. [#1614] +* Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -- Fix illegal chunked encoding. [#1615] +* Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -- Migrate cookie handling to `cookie` crate. [#1558] -- Update `sha-1` to 0.9. [#1586] -- Fix leak in client pool. [#1580] -- MSRV is now 1.41.1. +* Migrate cookie handling to `cookie` crate. [#1558] +* Update `sha-1` to 0.9. [#1586] +* Fix leak in client pool. [#1580] +* MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -- Bump minimum supported Rust version to 1.40 -- content_length function is removed, and you can set Content-Length by calling +* Bump minimum supported Rust version to 1.40 +* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -- Update `base64` dependency to 0.12 +* Update `base64` dependency to 0.12 ### Fixed -- Support parsing of `SameSite=None` [#1503] +* Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -- Correct spelling of ConnectError::Unresolved [#1487] -- Fix a mistake in the encoding of websocket continuation messages wherein +* Correct spelling of ConnectError::Unresolved [#1487] +* Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -- Implement `std::error::Error` for our custom errors [#1422] -- Remove `failure` support for `ResponseError` since that crate +* Implement `std::error::Error` for our custom errors [#1422] +* Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -- client::Connector accepts initial_window_size and initial_connection_window_size +* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -- Update the `time` dependency to 0.2.7. -- Moved actors messages support from actix crate, enabled with feature `actors`. -- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +* Update the `time` dependency to 0.2.7. +* Moved actors messages support from actix crate, enabled with feature `actors`. +* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -- MessageBody is not implemented for &'static [u8] anymore. +* MessageBody is not implemented for &'static [u8] anymore. ### Fixed -- Allow `SameSite=None` cookies to be sent in a response. +* Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -- Poll upgrade service's readiness from HTTP service handlers -- Replace brotli with brotli2 #1224 +* Poll upgrade service's readiness from HTTP service handlers +* Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -- Add websockets continuation frame support +* Add websockets continuation frame support ### Changed -- Replace `flate2-xxx` features with `compress` +* Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -- Check `Upgrade` service readiness before calling it -- Fix buffer remaining capacity calculation +* Check `Upgrade` service readiness before calling it +* Fix buffer remaining capacity calculation ### Changed -- Websockets: Ping and Pong should have binary data #1049 +* Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -- Add impl ResponseBuilder for Error +* Add impl ResponseBuilder for Error ### Changed -- Use rust based brotli compression library +* Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -- Migrate to tokio 0.2 -- Migrate to `std::future` +* Migrate to tokio 0.2 +* Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -- Add an additional `filename*` param in the `Content-Disposition` header of +* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -- Allow to use `std::convert::Infallible` as `actix_http::error::Error` +* Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -- h2 will use error response #1080 -- on_connect result isn't added to request extensions for http2 requests #1009 +* h2 will use error response #1080 +* on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -- Update percent-encoding to 2.1 -- Update serde_urlencoded to 0.6.1 +* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +* Update percent-encoding to 2.1 +* Update serde_urlencoded to 0.6.1 ### Fixed -- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -- Add `rustls` support -- Add `Clone` impl for `HeaderMap` +* Add `rustls` support +* Add `Clone` impl for `HeaderMap` ### Fixed -- awc client panic #1016 -- Invalid response with compression middleware enabled, but compression-related features +* awc client panic #1016 +* Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -- Add support for downcasting response errors #986 +* Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -- Replace `ClonableService` with local copy -- Upgrade `rand` dependency version to 0.7 +* Replace `ClonableService` with local copy +* Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -- Use `encoding_rs` crate instead of unmaintained `encoding` crate -- Add `Copy` and `Clone` impls for `ws::Codec` +* Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -- Do not compress NoContent (204) responses #918 +* Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -- Debug impl for ResponseBuilder -- From SizedStream and BodyStream for Body +* Debug impl for ResponseBuilder +* From SizedStream and BodyStream for Body ### Changed -- SizedStream uses u64 +* SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -- Parse incoming stream before closing stream on disconnect #868 +* Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -- Handle socket read disconnect +* Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -- Update actix-service to 0.4 -- Expect and upgrade services accept `ServerConfig` config. +* Update actix-service to 0.4 +* Expect and upgrade services accept `ServerConfig` config. ### Deleted -- `OneRequest` service +* `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -- Clean up response extensions in response pool #817 +* Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -- Allow to render h1 request headers in `Camel-Case` +* Allow to render h1 request headers in `Camel-Case` ### Fixed -- Read until eof for http/1.0 responses #771 +* Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -- Fix http client pool management -- Fix http client wait queue management #794 +* Fix http client pool management +* Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -- Fix BorrowMutError panic in client connector #793 +* Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -- Cookie::max_age() accepts value in seconds -- Cookie::max_age_time() accepts value in time::Duration -- Allow to specify server address for client connector +* Cookie::max_age() accepts value in seconds +* Cookie::max_age_time() accepts value in time::Duration +* Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -- `actix_http::encoding` always available -- use trust-dns-resolver 0.11.0 +* `actix_http::encoding` always available +* use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -- Allow to use custom service for upgrade requests -- Added `h1::SendResponse` future. +* Allow to use custom service for upgrade requests +* Added `h1::SendResponse` future. ### Changed -- MessageBody::length() renamed to MessageBody::size() for consistency -- ws handshake verification functions take RequestHead instead of Request +* MessageBody::length() renamed to MessageBody::size() for consistency +* ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -- Allow to use custom `Expect` handler -- Add minimal `std::error::Error` impl for `Error` +* Allow to use custom `Expect` handler +* Add minimal `std::error::Error` impl for `Error` ### Changed -- Export IntoHeaderValue -- Render error and return as response body -- Use thread pool for response body compression +* Export IntoHeaderValue +* Render error and return as response body +* Use thread pool for response body compression ### Deleted -- Removed PayloadBuffer +* Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -- Warn when an unsealed private cookie isn't valid UTF-8 +* Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -- Rust 1.31.0 compatibility -- Preallocate read buffer for h1 codec -- Detect socket disconnection during protocol selection +* Rust 1.31.0 compatibility +* Preallocate read buffer for h1 codec +* Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -- Added ws::Message::Nop, no-op websockets message +* Added ws::Message::Nop, no-op websockets message ### Changed -- Do not use thread pool for decompression if chunk size is smaller than 2048. +* Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index e58c3ee24..8d9c1640f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -- No significant changes since `0.4.0-beta.9`. +* No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -- Added `MultipartError::NoContentDisposition` variant. [#2451] -- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -- Added `Field::name` method for getting the field name. [#2451] -- `MultipartError` now marks variants with inner errors as the source. [#2451] -- `MultipartError` is now marked as non-exhaustive. [#2451] +* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +* Added `MultipartError::NoContentDisposition` variant. [#2451] +* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +* Added `Field::name` method for getting the field name. [#2451] +* `MultipartError` now marks variants with inner errors as the source. [#2451] +* `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -- No notable changes. +* No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -- No notable changes. +* No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -- Fix multipart consuming payload before header checks. [#1513] -- Update `bytes` to `1.0`. [#1813] +* Fix multipart consuming payload before header checks. [#1513] +* Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -- No significant changes from `0.3.0-beta.2`. +* No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -- Update `actix-*` dependencies to latest versions. +* Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -- Update `actix-web` to 3.0.0-beta.1 +* Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -- Update `actix-web` to 3.0.0-alpha.3 -- Bump minimum supported Rust version to 1.40 -- Minimize `futures` dependencies -- Remove the unused `time` dependency -- Fix missing `std::error::Error` implement for `MultipartError`. +* Update `actix-web` to 3.0.0-alpha.3 +* Bump minimum supported Rust version to 1.40 +* Minimize `futures` dependencies +* Remove the unused `time` dependency +* Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -- Release +* Release ## [0.2.0-alpha.4] - 2019-12-xx -- Multipart handling now handles Pending during read of boundary #1205 +* Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -- Migrate to `std::future` +* Migrate to `std::future` ## [0.1.4] - 2019-09-12 -- Multipart handling now parses requests which do not end in CRLF #1038 +* Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -- Fix ring dependency from actix-web default features for #741. +* Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -- Fix boundary parsing #876 +* Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -- Fix disconnect handling #834 +* Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -- Release +* Release ## [0.1.0-beta.4] - 2019-05-12 -- Handle cancellation of uploads #736 +* Handle cancellation of uploads #736 -- Upgrade to actix-web 1.0.0-beta.4 +* Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -- Do not support nested multipart +* Do not support nested multipart -- Split multipart support to separate crate +* Split multipart support to separate crate -- Optimize multipart handling #634, #769 +* Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 0a6a56359..d0ed55c88 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -- Minimum supported Rust version (MSRV) is now 1.52. +* Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -- Introduce `ResourceDef::join`. [#380] -- Disallow prefix routes with tail segments. [#379] -- Enforce path separators on dynamic prefixes. [#378] -- Improve malformed path error message. [#384] -- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -- Prefix segments with trailing slashes define a trailing empty segment. [#2355] -- Support multi-pattern prefixes and joins. [#2356] -- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -- Support `build_resource_path` on multi-pattern resources. [#2356] -- Minimum supported Rust version (MSRV) is now 1.51. +* Introduce `ResourceDef::join`. [#380] +* Disallow prefix routes with tail segments. [#379] +* Enforce path separators on dynamic prefixes. [#378] +* Improve malformed path error message. [#384] +* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +* Prefix segments with trailing slashes define a trailing empty segment. [#2355] +* Support multi-pattern prefixes and joins. [#2356] +* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +* Support `build_resource_path` on multi-pattern resources. [#2356] +* Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -- Fix `ResourceDef` `PartialEq` implementation. [#373] -- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -- Rename `Router::{*_checked => *_fn}`. [#373] -- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +* Fix `ResourceDef` `PartialEq` implementation. [#373] +* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +* Rename `Router::{*_checked => *_fn}`. [#373] +* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -- Path tail patterns now match new lines (`\n`) in request URL. [#360] -- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +* Path tail patterns now match new lines (`\n`) in request URL. [#360] +* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -- Add `Router::recognize_checked` [#247] +* Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -- Use `bytestring` version range compatible with Bytes v1.0. [#246] +* Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -- Fix `from_hex()` method +* Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -- Add `ResourceDef::resource_path_named()` path generation method +* Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -- Add impl `IntoPattern` for `&String` +* Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -- Use `IntoPattern` for `RouterBuilder::path()` +* Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -- Add `IntoPattern` trait -- Add multi-pattern resources +* Add `IntoPattern` trait +* Add multi-pattern resources ## 0.2.0 - 2019-12-07 -- Update http to 0.2 -- Update regex to 1.3 -- Use bytestring instead of string +* Update http to 0.2 +* Update regex to 1.3 +* Use bytestring instead of string ## 0.1.5 - 2019-05-15 -- Remove debug prints +* Remove debug prints ## 0.1.4 - 2019-05-15 -- Fix checked resource match +* Fix checked resource match ## 0.1.3 - 2019-04-22 -- Added support for `remainder match` (i.e "/path/{tail}*") +* Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -- Export `Quoter` type -- Allow to reset `Path` instance +* Export `Quoter` type +* Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -- Get dynamic segment by name instead of iterator. +* Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -- Initial release +* Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index e3deeb3f4..ef78ac54a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -- Re-export `actix_http::body::to_bytes`. [#2518] -- Update `actix_web::test` re-exports. [#2518] +* Re-export `actix_http::body::to_bytes`. [#2518] +* Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -- No significant changes since `0.1.0-beta.7`. +* No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -- No significant changes from `0.1.0-beta.5`. +* No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -- Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. +* Updated rustls to v0.20. [#2414] +* Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -- No significant changes from `0.1.0-beta.2`. +* No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -- No significant changes from `0.1.0-beta.1`. +* No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -- Move integration testing structs from `actix-web`. [#2112] +* Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 6abfe2c61..d3078499c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -- Minimum supported Rust version (MSRV) is now 1.52. +* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +* Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -- Minimum supported Rust version (MSRV) is now 1.51. +* Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -- Update `actix` to `0.12`. [#2277] +* Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -- No notable changes. +* No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -- No notable changes. +* No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -- No notable changes. +* No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -- No notable changes. +* No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -- Update `pin-project` to `1.0`. -- Update `bytes` to `1.0`. [#1813] -- `WebsocketContext::text` now takes an `Into`. [#1864] +* Update `pin-project` to `1.0`. +* Update `bytes` to `1.0`. [#1813] +* `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -- No significant changes from `3.0.0-beta.2`. +* No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -- Update `actix-*` dependencies to latest versions. +* Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -- Update `actix-web` & `actix-http` dependencies to beta.1 -- Bump minimum supported Rust version to 1.40 +* Update `actix-web` & `actix-http` dependencies to beta.1 +* Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -- Update the actix-web dependency to 3.0.0-alpha.1 -- Update the actix dependency to 0.10.0-alpha.2 -- Update the actix-http dependency to 2.0.0-alpha.3 +* Update the actix-web dependency to 3.0.0-alpha.1 +* Update the actix dependency to 0.10.0-alpha.2 +* Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -- Release +* Release ## [2.0.0-alpha.1] - 2019-12-15 -- Migrate to actix-web 2.0.0 +* Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -- Allow comma-separated websocket subprotocols without spaces (#1172) +* Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -- Update actix-web and actix-http dependencies +* Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -- Add `ws::start_with_addr()`, returning the address of the created actor, along +* Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -- Add support for specifying protocols on websocket handshake #835 +* Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -- Allow to use custom ws codec with `WebsocketContext` #925 +* Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -- Update actix-http and actix-web +* Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -- Initial impl +* Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 0d881d303..309274563 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -- No significant changes since `0.5.0-beta.5`. +* No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -- Improve error recovery potential when macro input is invalid. [#2410] -- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -- Minimum supported Rust version (MSRV) is now 1.52. +* Improve error recovery potential when macro input is invalid. [#2410] +* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +* Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -- In routing macros, paths are now validated at compile time. [#2350] -- Minimum supported Rust version (MSRV) is now 1.51. +* In routing macros, paths are now validated at compile time. [#2350] +* Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -- No notable changes. +* No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -- Preserve doc comments when using route macros. [#2022] -- Add `name` attribute to `route` macro. [#1934] +* Preserve doc comments when using route macros. [#2022] +* Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -- Use new call signature for `System::new`. +* Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -- Added compile success and failure testing. [#1677] -- Add `route` macro for supporting multiple HTTP methods guards. [#1674] +* Added compile success and failure testing. [#1677] +* Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -- No significant changes from `0.3.0-beta.1`. +* No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -- Add main entry-point macro that uses re-exported runtime. [#1559] +* Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -- Add resource middleware on actix-web-codegen [#1467] +* Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -- Allow the handler function to be named as `config` [#1290] +* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +* Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -- Generate code for actix-web 2.0 +* Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -- Bump up `syn` & `quote` to 1.0 -- Provide better error message +* Bump up `syn` & `quote` to 1.0 +* Provide better error message ## 0.1.2 - 2019-06-04 -- Add macros for head, options, trace, connect and patch http methods +* Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -- Add syn "extra-traits" feature +* Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -- Release +* Release ## 0.1.0-beta.1 - 2019-04-20 -- Gen code for actix-web 1.0.0-beta.1 +* Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -- Gen code for actix-web 1.0.0-alpha.6 +* Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -- Initial impl +* Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b5144b7a2..7b822930c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -- Rename `Connector::{ssl => openssl}`. [#2503] -- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +* Rename `Connector::{ssl => openssl}`. [#2503] +* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -- No significant changes since `3.0.0-beta.12`. +* No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] +* Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -- No significant changes from `3.0.0-beta.10`. +* No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -- No significant changes from `3.0.0-beta.9`. +* No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -- Updated rustls to v0.20. [#2414] +* Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -- Send headers within the redirect requests. [#2310] +* Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -- Change compression algorithm features flags. [#2250] +* Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -- No significant changes since 3.0.0-beta.5. +* No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -- Fix http/https encoding when enabling `compress` feature. [#2116] -- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +* Fix http/https encoding when enabling `compress` feature. [#2116] +* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -- Feature `cookies` is now optional and enabled by default. [#1981] -- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +* Feature `cookies` is now optional and enabled by default. [#1981] +* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -- `ClientBuilder::default` function [#2008] +* `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -- `ClientRequest::insert_header` method which allows using typed headers. [#1869] -- `ClientRequest::append_header` method which allows using typed headers. [#1869] -- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +* `ClientRequest::insert_header` method which allows using typed headers. [#1869] +* `ClientRequest::append_header` method which allows using typed headers. [#1869] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +* 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] +* `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 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -- Update `rand` to `0.8` -- Update `bytes` to `1.0`. [#1813] -- Update `rust-tls` to `0.19`. [#1813] +* Update `rand` to `0.8` +* Update `bytes` to `1.0`. [#1813] +* Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -- Ensure `actix-http` dependency uses same `serde_urlencoded`. +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -- Upgrade `serde_urlencoded` to `0.7`. [#1773] +* Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -- Upgrade `base64` to `0.13`. [#1744] -- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +* Upgrade `base64` to `0.13`. [#1744] +* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -- `Client::build` was renamed to `Client::builder`. +* `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -- Update actix-codec & actix-tls dependencies. +* Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -- Update `rustls` to 0.18 +* Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -- Update `actix-http` dependency to 2.0.0-beta.2 +* Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -- Update `actix-http` dependency to 2.0.0-beta.1 +* Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -- Implement `std::error::Error` for our custom errors [#1422] -- Bump minimum supported Rust version to 1.40 -- Update `base64` dependency to 0.12 +* Implement `std::error::Error` for our custom errors [#1422] +* Bump minimum supported Rust version to 1.40 +* Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -- Update `actix-http` dependency to 2.0.0-alpha.2 -- Update `rustls` dependency to 0.17 -- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -- ClientBuilder allowing to set max_http_version to limit HTTP version to be used +* Update `actix-http` dependency to 2.0.0-alpha.2 +* Update `rustls` dependency to 0.17 +* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +* ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -- Fix compilation with default features off +* Fix compilation with default features off ## [1.0.0] - 2019-12-13 -- Release +* Release ## [1.0.0-alpha.3] -- Migrate to `std::future` +* Migrate to `std::future` ## [0.2.8] - 2019-11-06 -- Add support for setting query from Serialize type for client request. +* Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -- Remaining getter methods for `ClientRequest`'s private `head` field #1101 +* Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -- Export frozen request related types. +* Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -- Add `FrozenClientRequest` to support retries for sending HTTP requests +* Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -- Ensure that the `Host` header is set when initiating a WebSocket client connection. +* Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -- Update percent-encoding to "2.1" +* Update percent-encoding to "2.1" -- Update serde_urlencoded to "0.6.1" +* Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -- Add `rustls` support +* Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -- Always append a colon after username in basic auth +* Always append a colon after username in basic auth -- Upgrade `rand` dependency version to 0.7 +* Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -- Add license files +* Add license files ## [0.2.0] - 2019-05-12 ### Added -- Allow to send headers in `Camel-Case` form. +* Allow to send headers in `Camel-Case` form. ### Changed -- Upgrade actix-http dependency. +* Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -- Allow to specify server address for http and ws requests. +* Allow to specify server address for http and ws requests. ### Changed -- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -- No changes +* No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -- Do not set default headers for websocket request +* Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -- Do not set any default headers +* Do not set any default headers ### Added -- Add Debug impl for BoxedSocket +* Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -- Update actix-http dependency +* Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -- Export `MessageBody` type +* Export `MessageBody` type -- `ClientResponse::json()` - Loads and parse `application/json` encoded body +* `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -- `ClientRequest::json()` accepts reference instead of object. +* `ClientRequest::json()` accepts reference instead of object. -- `ClientResponse::body()` does not consume response object. +* `ClientResponse::body()` does not consume response object. -- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -- Per request and session wide request timeout. +* Per request and session wide request timeout. -- Session wide headers. +* Session wide headers. -- Session wide basic and bearer auth. +* Session wide basic and bearer auth. -- Re-export `actix_http::client::Connector`. +* Re-export `actix_http::client::Connector`. ### Changed -- Allow to override request's uri +* Allow to override request's uri -- Export `ws` sub-module with websockets related types +* Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -- Initial impl +* Initial impl From de20d21703759d61af0836011210f6d7ffcf10c7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:21:30 +0000 Subject: [PATCH 208/861] use dash hyphenation in markdown --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- CHANGES.md | 552 +++++++++++++-------------- CODE_OF_CONDUCT.md | 20 +- MIGRATION.md | 156 ++++---- README.md | 60 +-- actix-files/CHANGES.md | 102 ++--- actix-http-test/CHANGES.md | 76 ++-- actix-http/CHANGES.md | 544 +++++++++++++------------- actix-http/README.md | 4 +- actix-multipart/CHANGES.md | 74 ++-- actix-router/CHANGES.md | 102 ++--- actix-test/CHANGES.md | 22 +- actix-web-actors/CHANGES.md | 60 +-- actix-web-codegen/CHANGES.md | 52 +-- awc/CHANGES.md | 170 ++++----- 15 files changed, 999 insertions(+), 999 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2df863ae8..fa06a137a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -33,5 +33,5 @@ Please search on the [Actix Web issue tracker](https://github.com/actix/actix-we ## Your Environment -* Rust Version (I.e, output of `rustc -V`): -* Actix Web Version: +- Rust Version (I.e, output of `rustc -V`): +- Actix Web Version: diff --git a/CHANGES.md b/CHANGES.md index 77ab2e218..8e030819f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,22 +5,22 @@ ## 4.0.0-beta.15 - 2021-12-17 ### Added -* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] -* Implement `Debug` for `DefaultHeaders`. [#2510] +- Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] +- Implement `Debug` for `DefaultHeaders`. [#2510] ### Changed -* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -* Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] -* Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] -* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -* Relax body type and error bounds on test utilities. [#2518] +- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +- Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] +- Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] +- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +- Relax body type and error bounds on test utilities. [#2518] ### Removed -* Top-level `EitherExtractError` export. [#2510] -* Conversion implementations for `either` crate. [#2516] -* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] +- Top-level `EitherExtractError` export. [#2510] +- Conversion implementations for `either` crate. [#2516] +- `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2515]: https://github.com/actix/actix-web/pull/2515 @@ -30,31 +30,31 @@ ## 4.0.0-beta.14 - 2021-12-11 ### Added -* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] -* `AcceptEncoding` typed header. [#2482] -* `Range` typed header. [#2485] -* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -* `HttpRequest::{req_data,req_data_mut}`. [#2487] -* `ServiceResponse::into_parts`. [#2499] +- Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] +- `AcceptEncoding` typed header. [#2482] +- `Range` typed header. [#2485] +- `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +- `HttpRequest::{req_data,req_data_mut}`. [#2487] +- `ServiceResponse::into_parts`. [#2499] ### Changed -* Rename `Accept::{mime_precedence => ranked}`. [#2480] -* Rename `Accept::{mime_preference => preference}`. [#2480] -* Un-deprecate `App::data_factory`. [#2484] -* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -* Remove `B` (body) type parameter on `App`. [#2493] -* Add `B` (body) type parameter on `Scope`. [#2492] -* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] +- Rename `Accept::{mime_precedence => ranked}`. [#2480] +- Rename `Accept::{mime_preference => preference}`. [#2480] +- Un-deprecate `App::data_factory`. [#2484] +- `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] +- Remove `B` (body) type parameter on `App`. [#2493] +- Add `B` (body) type parameter on `Scope`. [#2492] +- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] ### Fixed -* Accept wildcard `*` items in `AcceptLanguage`. [#2480] -* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] -* Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +- Typed headers containing lists that require one or more items now enforce this minimum. [#2482] ### Removed -* `ConnectionInfo::get`. [#2487] +- `ConnectionInfo::get`. [#2487] [#2430]: https://github.com/actix/actix-web/pull/2430 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -71,20 +71,20 @@ ## 4.0.0-beta.13 - 2021-11-30 ### Changed -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 4.0.0-beta.12 - 2021-11-22 ### Changed -* Compress middleware's response type is now `AnyBody>`. [#2448] +- Compress middleware's response type is now `AnyBody>`. [#2448] ### Fixed -* Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] +- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] ### Removed -* `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] +- `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -92,11 +92,11 @@ ## 4.0.0-beta.11 - 2021-11-15 ### Added -* Re-export `dev::ServerHandle` from `actix-server`. [#2442] +- Re-export `dev::ServerHandle` from `actix-server`. [#2442] ### Changed -* `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] [#2423]: https://github.com/actix/actix-web/pull/2423 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -104,18 +104,18 @@ ## 4.0.0-beta.10 - 2021-10-20 ### Added -* Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] -* `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] +- `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] ### Changed -* Associated type `FromRequest::Config` was removed. [#2233] -* Inner field made private on `web::Payload`. [#2384] -* `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Associated type `FromRequest::Config` was removed. [#2233] +- Inner field made private on `web::Payload`. [#2384] +- `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. ### Removed -* Useless `ServiceResponse::checked_expr` method. [#2401] +- Useless `ServiceResponse::checked_expr` method. [#2401] [#2233]: https://github.com/actix/actix-web/pull/2233 [#2362]: https://github.com/actix/actix-web/pull/2362 @@ -128,17 +128,17 @@ ## 4.0.0-beta.9 - 2021-09-09 ### Added -* Re-export actix-service `ServiceFactory` in `dev` module. [#2325] +- Re-export actix-service `ServiceFactory` in `dev` module. [#2325] ### Changed -* Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -* Move `BaseHttpResponse` to `dev::Response`. [#2379] -* Enable `TestRequest::param` to accept more than just static strings. [#2172] -* Minimum supported Rust version (MSRV) is now 1.51. +- Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] +- Move `BaseHttpResponse` to `dev::Response`. [#2379] +- Enable `TestRequest::param` to accept more than just static strings. [#2172] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Fix quality parse error in Accept-Encoding header. [#2344] -* Re-export correct type at `web::HttpResponse`. [#2379] +- Fix quality parse error in Accept-Encoding header. [#2344] +- Re-export correct type at `web::HttpResponse`. [#2379] [#2172]: https://github.com/actix/actix-web/pull/2172 [#2325]: https://github.com/actix/actix-web/pull/2325 @@ -148,18 +148,18 @@ ## 4.0.0-beta.8 - 2021-06-26 ### Added -* Add `ServiceRequest::parts_mut`. [#2177] -* Add extractors for `Uri` and `Method`. [#2263] -* Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] -* Add `Route::service` for using hand-written services as handlers. [#2262] +- Add `ServiceRequest::parts_mut`. [#2177] +- Add extractors for `Uri` and `Method`. [#2263] +- Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +- Add `Route::service` for using hand-written services as handlers. [#2262] ### Changed -* Change compression algorithm features flags. [#2250] -* Deprecate `App::data` and `App::data_factory`. [#2271] -* Smarter extraction of `ConnectionInfo` parts. [#2282] +- Change compression algorithm features flags. [#2250] +- Deprecate `App::data` and `App::data_factory`. [#2271] +- Smarter extraction of `ConnectionInfo` parts. [#2282] ### Fixed -* Scope and Resource middleware can access data items set on their own layer. [#2288] +- Scope and Resource middleware can access data items set on their own layer. [#2288] [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -172,23 +172,23 @@ ## 4.0.0-beta.7 - 2021-06-17 ### Added -* `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] +- `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] ### Changed -* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] +- Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] [#2162]: (https://github.com/actix/actix-web/pull/2162) -* `ServiceResponse::error_response` now uses body type of `Body`. [#2201] -* `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -* Update `language-tags` to `0.3`. -* `ServiceResponse::take_body`. [#2201] -* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] +- `ServiceResponse::error_response` now uses body type of `Body`. [#2201] +- `ServiceResponse::checked_expr` now returns a `Result`. [#2201] +- Update `language-tags` to `0.3`. +- `ServiceResponse::take_body`. [#2201] +- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] ### Removed -* `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] +- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 @@ -198,11 +198,11 @@ ## 4.0.0-beta.6 - 2021-04-17 ### Added -* `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +- `HttpResponse` and `HttpResponseBuilder` structs. [#2065] ### Changed -* Most error types are now marked `#[non_exhaustive]`. [#2148] -* Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. +- Most error types are now marked `#[non_exhaustive]`. [#2148] +- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -210,20 +210,20 @@ ## 4.0.0-beta.5 - 2021-04-02 ### Added -* `Header` extractor for extracting common HTTP headers in handlers. [#2094] -* Added `TestServer::client_headers` method. [#2097] +- `Header` extractor for extracting common HTTP headers in handlers. [#2094] +- Added `TestServer::client_headers` method. [#2097] ### Fixed -* Double ampersand in Logger format is escaped correctly. [#2067] +- Double ampersand in Logger format is escaped correctly. [#2067] ### Changed -* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed +- `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed -* The `client` mod was removed. Clients should now use `awc` directly. +- The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) -* Integration testing was moved to new `actix-test` crate. Namely these items from the `test` +- Integration testing was moved to new `actix-test` crate. Namely these items from the `test` module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] [#2067]: https://github.com/actix/actix-web/pull/2067 @@ -235,8 +235,8 @@ ## 4.0.0-beta.4 - 2021-03-09 ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default +- Feature `cookies` is now optional and enabled by default. [#1981] +- `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -244,36 +244,36 @@ ## 4.0.0-beta.3 - 2021-02-10 -* Update `actix-web-codegen` to `0.5.0-beta.1`. +- Update `actix-web-codegen` to `0.5.0-beta.1`. ## 4.0.0-beta.2 - 2021-02-10 ### Added -* The method `Either, web::Form>::into_inner()` which returns the inner type for +- The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] -* Add `services!` macro for helping register multiple services to `App`. [#1933] -* Enable registering a vec of services of the same type to `App` [#1933] +- Add `services!` macro for helping register multiple services to `App`. [#1933] +- Enable registering a vec of services of the same type to `App` [#1933] ### Changed -* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. +- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. Making it simpler and more performant. [#1891] -* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] -* `ServiceRequest::from_request` can no longer fail. [#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` +- `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] +- `ServiceRequest::from_request` can no longer fail. [#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` provide `&Service` in closure +- `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure argument. [#1905] -* `web::block` no longer requires the output is a Result. [#1957] +- `web::block` no longer requires the output is a Result. [#1957] ### Fixed -* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] +- Multiple calls to `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 +- 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] @@ -289,26 +289,26 @@ ## 4.0.0-beta.1 - 2021-01-07 ### Added -* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and +- `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `rust-tls` to `0.19`. [#1813] -* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `rust-tls` to `0.19`. [#1813] +- Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration guide for implications. [#1875] -* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -* MSRV is now 1.46.0. +- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +- MSRV is now 1.46.0. ### Fixed -* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] ### Removed -* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now +- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now exposed directly by the `middleware` module. -* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 @@ -321,16 +321,16 @@ ## 3.3.3 - 2021-12-18 ### Changed -* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] +- Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] [#2529]: https://github.com/actix/actix-web/pull/2529 ## 3.3.2 - 2020-12-01 ### Fixed -* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] -* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] -* Increase minimum `socket2` version. [#1803] +- Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] +- Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] +- Increase minimum `socket2` version. [#1803] [#1762]: https://github.com/actix/actix-web/pull/1762 [#1798]: https://github.com/actix/actix-web/pull/1798 @@ -338,15 +338,15 @@ ## 3.3.1 - 2020-11-29 -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 ### Added -* Add `Either` extractor helper. [#1788] +- Add `Either` extractor helper. [#1788] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 @@ -354,17 +354,17 @@ ## 3.2.0 - 2020-10-30 ### Added -* Implement `exclude_regex` for Logger middleware. [#1723] -* Add request-local data extractor `web::ReqData`. [#1748] -* Add ability to register closure for request middleware logging. [#1749] -* Add `app_data` to `ServiceConfig`. [#1757] -* Expose `on_connect` for access to the connection stream before request is handled. [#1754] +- Implement `exclude_regex` for Logger middleware. [#1723] +- Add request-local data extractor `web::ReqData`. [#1748] +- Add ability to register closure for request middleware logging. [#1749] +- Add `app_data` to `ServiceConfig`. [#1757] +- Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed -* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -* Print non-configured `Data` type when attempting extraction. [#1743] -* Re-export bytes::Buf{Mut} in web module. [#1750] -* Upgrade `pin-project` to `1.0`. +- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Print non-configured `Data` type when attempting extraction. [#1743] +- Re-export bytes::Buf{Mut} in web module. [#1750] +- Upgrade `pin-project` to `1.0`. [#1723]: https://github.com/actix/actix-web/pull/1723 [#1743]: https://github.com/actix/actix-web/pull/1743 @@ -376,13 +376,13 @@ ## 3.1.0 - 2020-09-29 ### Changed -* Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` +- Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` to retain any trailing slashes. [#1695] -* Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` +- Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] ### Fixed -* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] +- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] [#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 @@ -391,33 +391,33 @@ ## 3.0.2 - 2020-09-15 ### Fixed -* `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] +- `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] [#1678]: https://github.com/actix/actix-web/pull/1678 ## 3.0.1 - 2020-09-13 ### Changed -* `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] +- `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] [#1673]: https://github.com/actix/actix-web/pull/1673 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.4`. +- No significant changes from `3.0.0-beta.4`. ## 3.0.0-beta.4 - 2020-09-09 ### Added -* `middleware::NormalizePath` now has configurable behavior for either always having a trailing +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed -* Update actix-codec and actix-utils dependencies. [#1634] -* `FormConfig` and `JsonConfig` configurations are now also considered when set +- Update actix-codec and actix-utils dependencies. [#1634] +- `FormConfig` and `JsonConfig` configurations are now also considered when set using `App::data`. [#1641] -* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -* `HttpServer::maxconnrate` is renamed to the more expressive +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] +- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. [#1655] [#1639]: https://github.com/actix/actix-web/pull/1639 @@ -427,22 +427,22 @@ ## 3.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 3.0.0-beta.2 - 2020-08-17 ### Changed -* `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set +- `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set using `App::data`. [#1610] -* `web::Path` now has a public representation: `web::Path(pub T)` that enables +- `web::Path` now has a public representation: `web::Path(pub T)` that enables destructuring. [#1594] -* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to +- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to access `HttpRequest` which already allows this. [#1618] -* Re-export all error types from `awc`. [#1621] -* MSRV is now 1.42.0. +- Re-export all error types from `awc`. [#1621] +- MSRV is now 1.42.0. ### Fixed -* Memory leak of app data in pooled requests. [#1609] +- Memory leak of app data in pooled requests. [#1609] [#1594]: https://github.com/actix/actix-web/pull/1594 [#1609]: https://github.com/actix/actix-web/pull/1609 @@ -453,29 +453,29 @@ ## 3.0.0-beta.1 - 2020-07-13 ### Added -* Re-export `actix_rt::main` as `actix_web::main`. -* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched +- Re-export `actix_rt::main` as `actix_web::main`. +- `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched resource pattern. -* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. +- `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. ### Changed -* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. -* MSRV is now 1.41.1 +- Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] +- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +- MSRV is now 1.41.1 ### Fixed -* `NormalizePath` improved consistency when path needs slashes added _and_ removed. +- `NormalizePath` improved consistency when path needs slashes added _and_ removed. ## 3.0.0-alpha.3 - 2020-05-21 ### Added -* Add option to create `Data` from `Arc` [#1509] +- Add option to create `Data` from `Arc` [#1509] ### Changed -* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -* Fix audit issue logging by default peer address [#1485] -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` +- Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] +- Fix audit issue logging by default peer address [#1485] +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` [#1485]: https://github.com/actix/actix-web/pull/1485 [#1509]: https://github.com/actix/actix-web/pull/1509 @@ -484,10 +484,10 @@ ### Changed -* `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] -* Implement `std::error::Error` for our custom errors [#1422] -* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] -* Remove the `failure` feature and support. +- `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] +- Implement `std::error::Error` for our custom errors [#1422] +- NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] +- Remove the `failure` feature and support. [#1422]: https://github.com/actix/actix-web/pull/1422 [#1433]: https://github.com/actix/actix-web/pull/1433 @@ -499,16 +499,16 @@ ### Added -* Add helper function for creating routes with `TRACE` method guard `web::trace()` -* Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. +- Add helper function for creating routes with `TRACE` method guard `web::trace()` +- Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. ### Changed -* Use `sha-1` crate instead of unmaintained `sha1` crate -* Skip empty chunks when returning response from a `Stream` [#1308] -* Update the `time` dependency to 0.2.7 -* Update `actix-tls` dependency to 2.0.0-alpha.1 -* Update `rustls` dependency to 0.17 +- Use `sha-1` crate instead of unmaintained `sha1` crate +- Skip empty chunks when returning response from a `Stream` [#1308] +- Update the `time` dependency to 0.2.7 +- Update `actix-tls` dependency to 2.0.0-alpha.1 +- Update `rustls` dependency to 0.17 [#1308]: https://github.com/actix/actix-web/pull/1308 @@ -516,408 +516,408 @@ ### Changed -* Rename `HttpServer::start()` to `HttpServer::run()` +- Rename `HttpServer::start()` to `HttpServer::run()` -* Allow to gracefully stop test server via `TestServer::stop()` +- Allow to gracefully stop test server via `TestServer::stop()` -* Allow to specify multi-patterns for resources +- Allow to specify multi-patterns for resources ## [2.0.0-rc] - 2019-12-20 ### Changed -* Move `BodyEncoding` to `dev` module #1220 +- Move `BodyEncoding` to `dev` module #1220 -* Allow to set `peer_addr` for TestRequest #1074 +- Allow to set `peer_addr` for TestRequest #1074 -* Make web::Data deref to Arc #1214 +- Make web::Data deref to Arc #1214 -* Rename `App::register_data()` to `App::app_data()` +- Rename `App::register_data()` to `App::app_data()` -* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` +- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` ### Fixed -* Fix `AppConfig::secure()` is always false. #1202 +- Fix `AppConfig::secure()` is always false. #1202 ## [2.0.0-alpha.6] - 2019-12-15 ### Fixed -* Fixed compilation with default features off +- Fixed compilation with default features off ## [2.0.0-alpha.5] - 2019-12-13 ### Added -* Add test server, `test::start()` and `test::start_with()` +- Add test server, `test::start()` and `test::start_with()` ## [2.0.0-alpha.4] - 2019-12-08 ### Deleted -* Delete HttpServer::run(), it is not useful with async/await +- Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 ### Changed -* Migrate to tokio 0.2 +- Migrate to tokio 0.2 ## [2.0.0-alpha.1] - 2019-11-22 ### Changed -* Migrated to `std::future` +- Migrated to `std::future` -* Remove implementation of `Responder` for `()`. (#1167) +- Remove implementation of `Responder` for `()`. (#1167) ## [1.0.9] - 2019-11-14 ### Added -* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) +- Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) ### Changed -* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) +- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) ## [1.0.8] - 2019-09-25 ### Added -* Add `Scope::register_data` and `Resource::register_data` methods, parallel to +- Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. -* Add `middleware::Condition` that conditionally enables another middleware +- Add `middleware::Condition` that conditionally enables another middleware -* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` +- Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, +- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, which is useful for example with systemd. ### Changed -* Make UrlEncodedError::Overflow more informative +- Make UrlEncodedError::Overflow more informative -* Use actix-testing for testing utils +- Use actix-testing for testing utils ## [1.0.7] - 2019-08-29 ### Fixed -* Request Extensions leak #1062 +- Request Extensions leak #1062 ## [1.0.6] - 2019-08-28 ### Added -* Re-implement Host predicate (#989) +- Re-implement Host predicate (#989) -* Form implements Responder, returning a `application/x-www-form-urlencoded` response +- Form implements Responder, returning a `application/x-www-form-urlencoded` response -* Add `into_inner` to `Data` +- Add `into_inner` to `Data` -* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set +- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set the header in test requests. ### Changed -* `Query` payload made `pub`. Allows user to pattern-match the payload. +- `Query` payload made `pub`. Allows user to pattern-match the payload. -* Enable `rust-tls` feature for client #1045 +- Enable `rust-tls` feature for client #1045 -* Update serde_urlencoded to 0.6.1 +- Update serde_urlencoded to 0.6.1 -* Update url to 2.1 +- Update url to 2.1 ## [1.0.5] - 2019-07-18 ### Added -* Unix domain sockets (HttpServer::bind_uds) #92 +- Unix domain sockets (HttpServer::bind_uds) #92 -* Actix now logs errors resulting in "internal server error" responses always, with the `error` +- Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level ### Fixed -* Restored logging of errors through the `Logger` middleware +- Restored logging of errors through the `Logger` middleware ## [1.0.4] - 2019-07-17 ### Added -* Add `Responder` impl for `(T, StatusCode) where T: Responder` +- Add `Responder` impl for `(T, StatusCode) where T: Responder` -* Allow to access app's resource map via +- Allow to access app's resource map via `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. ### Changed -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [1.0.3] - 2019-06-28 ### Added -* Support asynchronous data factories #850 +- Support asynchronous data factories #850 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Use `encoding_rs` crate instead of unmaintained `encoding` crate ## [1.0.2] - 2019-06-17 ### Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. -* Move identity middleware to `actix-identity` crate. +- Move identity middleware to `actix-identity` crate. ## [1.0.1] - 2019-06-17 ### Added -* Add support for PathConfig #903 +- Add support for PathConfig #903 -* Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. +- Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. ### Changed -* Move cors middleware to `actix-cors` crate. +- Move cors middleware to `actix-cors` crate. -* Move identity middleware to `actix-identity` crate. +- Move identity middleware to `actix-identity` crate. -* Disable default feature `secure-cookies`. +- Disable default feature `secure-cookies`. -* Allow to test an app that uses async actors #897 +- Allow to test an app that uses async actors #897 -* Re-apply patch from #637 #894 +- Re-apply patch from #637 #894 ### Fixed -* HttpRequest::url_for is broken with nested scopes #915 +- HttpRequest::url_for is broken with nested scopes #915 ## [1.0.0] - 2019-06-05 ### Added -* Add `Scope::configure()` method. +- Add `Scope::configure()` method. -* Add `ServiceRequest::set_payload()` method. +- Add `ServiceRequest::set_payload()` method. -* Add `test::TestRequest::set_json()` convenience method to automatically +- Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ### Changed -* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 +- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 ### Fixed -* Fix Logger request time format, and use rfc3339. #867 +- Fix Logger request time format, and use rfc3339. #867 -* Clear http requests pool on app service drop #860 +- Clear http requests pool on app service drop #860 ## [1.0.0-rc] - 2019-05-18 ### Added -* Add `Query::from_query()` to extract parameters from a query string. #846 -* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. +- Add `Query::from_query()` to extract parameters from a query string. #846 +- `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changed -* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. +- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. ### Fixed -* Codegen with parameters in the path only resolves the first registered endpoint #841 +- Codegen with parameters in the path only resolves the first registered endpoint #841 ## [1.0.0-beta.4] - 2019-05-12 ### Added -* Allow to set/override app data on scope level +- Allow to set/override app data on scope level ### Changed -* `App::configure` take an `FnOnce` instead of `Fn` -* Upgrade actix-net crates +- `App::configure` take an `FnOnce` instead of `Fn` +- Upgrade actix-net crates ## [1.0.0-beta.3] - 2019-05-04 ### Added -* Add helper function for executing futures `test::block_fn()` +- Add helper function for executing futures `test::block_fn()` ### Changed -* Extractor configuration could be registered with `App::data()` +- Extractor configuration could be registered with `App::data()` or with `Resource::data()` #775 -* Route data is unified with app data, `Route::data()` moved to resource +- Route data is unified with app data, `Route::data()` moved to resource level to `Resource::data()` -* CORS handling without headers #702 +- CORS handling without headers #702 -* Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. +- Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. ### Fixed -* Fix `NormalizePath` middleware impl #806 +- Fix `NormalizePath` middleware impl #806 ### Deleted -* `App::data_factory()` is deleted. +- `App::data_factory()` is deleted. ## [1.0.0-beta.2] - 2019-04-24 ### Added -* Add raw services support via `web::service()` +- Add raw services support via `web::service()` -* Add helper functions for reading response body `test::read_body()` +- Add helper functions for reading response body `test::read_body()` -* Add support for `remainder match` (i.e "/path/{tail}*") +- Add support for `remainder match` (i.e "/path/{tail}*") -* Extend `Responder` trait, allow to override status code and headers. +- Extend `Responder` trait, allow to override status code and headers. -* Store visit and login timestamp in the identity cookie #502 +- Store visit and login timestamp in the identity cookie #502 ### Changed -* `.to_async()` handler can return `Responder` type #792 +- `.to_async()` handler can return `Responder` type #792 ### Fixed -* Fix async web::Data factory handling +- Fix async web::Data factory handling ## [1.0.0-beta.1] - 2019-04-20 ### Added -* Add helper functions for reading test response body, +- Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` -* Add `.peer_addr()` #744 +- Add `.peer_addr()` #744 -* Add `NormalizePath` middleware +- Add `NormalizePath` middleware ### Changed -* Rename `RouterConfig` to `ServiceConfig` +- Rename `RouterConfig` to `ServiceConfig` -* Rename `test::call_success` to `test::call_service` +- Rename `test::call_success` to `test::call_service` -* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. -* `CookieIdentityPolicy::max_age()` accepts value in seconds +- `CookieIdentityPolicy::max_age()` accepts value in seconds ### Fixed -* Fixed `TestRequest::app_data()` +- Fixed `TestRequest::app_data()` ## [1.0.0-alpha.6] - 2019-04-14 ### Changed -* Allow using any service as default service. +- Allow using any service as default service. -* Remove generic type for request payload, always use default. +- Remove generic type for request payload, always use default. -* Removed `Decompress` middleware. Bytes, String, Json, Form extractors +- Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. -* Make extractor config type explicit. Add `FromRequest::Config` associated type. +- Make extractor config type explicit. Add `FromRequest::Config` associated type. ## [1.0.0-alpha.5] - 2019-04-12 ### Added -* Added async io `TestBuffer` for testing. +- Added async io `TestBuffer` for testing. ### Deleted -* Removed native-tls support +- Removed native-tls support ## [1.0.0-alpha.4] - 2019-04-08 ### Added -* `App::configure()` allow to offload app configuration to different methods +- `App::configure()` allow to offload app configuration to different methods -* Added `URLPath` option for logger +- Added `URLPath` option for logger -* Added `ServiceRequest::app_data()`, returns `Data` +- Added `ServiceRequest::app_data()`, returns `Data` -* Added `ServiceFromRequest::app_data()`, returns `Data` +- Added `ServiceFromRequest::app_data()`, returns `Data` ### Changed -* `FromRequest` trait refactoring +- `FromRequest` trait refactoring -* Move multipart support to actix-multipart crate +- Move multipart support to actix-multipart crate ### Fixed -* Fix body propagation in Response::from_error. #760 +- Fix body propagation in Response::from_error. #760 ## [1.0.0-alpha.3] - 2019-04-02 ### Changed -* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` +- Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` -* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` +- Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -* Removed `Deref` impls +- Removed `Deref` impls ### Removed -* Removed unused `actix_web::web::md()` +- Removed unused `actix_web::web::md()` ## [1.0.0-alpha.2] - 2019-03-29 ### Added -* Rustls support +- Rustls support ### Changed -* Use forked cookie +- Use forked cookie -* Multipart::Field renamed to MultipartField +- Multipart::Field renamed to MultipartField ## [1.0.0-alpha.1] - 2019-03-28 ### Changed -* Complete architecture re-design. +- Complete architecture re-design. -* Return 405 response if no matching route found within resource #538 +- Return 405 response if no matching route found within resource #538 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ae97b3240..dbd092095 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/MIGRATION.md b/MIGRATION.md index d53bd7bf8..338a04389 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,6 @@ ## Unreleased -* The default `NormalizePath` behavior now strips trailing slashes by default. This was +- The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. @@ -11,9 +11,9 @@ Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. -* The `type Config` of `FromRequest` was removed. +- The `type Config` of `FromRequest` was removed. -* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). +- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default all compression algorithms are enabled. To select algorithm you want to include with `middleware::Compress` use following flags: - `compress-brotli` @@ -28,30 +28,30 @@ ## 3.0.0 -* The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to +- The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to simply a `T`. To access a `Data` use `ServiceRequest::app_data::>()`. -* Cookie handling has been offloaded to the `cookie` crate: +- Cookie handling has been offloaded to the `cookie` crate: * `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs. * Some types now require lifetime parameters. -* The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects +- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects any `actix-web` method previously expecting a time v0.1 input. -* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now +- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now result in `SameSite=None` being sent with the response Set-Cookie header. To create a cookie without a SameSite attribute, remove any calls setting same_site. -* actix-http support for Actors messages was moved to actix-http crate and is enabled +- actix-http support for Actors messages was moved to actix-http crate and is enabled with feature `actors` -* content_length function is removed from actix-http. +- content_length function is removed from actix-http. You can set Content-Length by normally setting the response body or calling no_chunking function. -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use +- Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use destructuring or `.into_inner()`. For example: ```rust @@ -71,35 +71,35 @@ } ``` -* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. +- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`, or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`. -* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. -* `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. +- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. ## 2.0.0 -* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to +- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to `.await` on `run` method result, in that case it awaits server exit. -* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. +- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. Stored data is available via `HttpRequest::app_data()` method at runtime. -* Extractor configuration must be registered with `App::app_data()` instead of `App::data()` +- Extractor configuration must be registered with `App::app_data()` instead of `App::data()` -* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` +- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` replace `fn` with `async fn` to convert sync handler to async -* `actix_http_test::TestServer` moved to `actix_web::test` module. To start +- `actix_http_test::TestServer` moved to `actix_web::test` module. To start test server use `test::start()` or `test_start_with_config()` methods -* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders +- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders http response. -* Feature `rust-tls` renamed to `rustls` +- Feature `rust-tls` renamed to `rustls` instead of @@ -113,7 +113,7 @@ actix-web = { version = "2.0.0", features = ["rustls"] } ``` -* Feature `ssl` renamed to `openssl` +- Feature `ssl` renamed to `openssl` instead of @@ -126,11 +126,11 @@ ```rust actix-web = { version = "2.0.0", features = ["openssl"] } ``` -* `Cors` builder now requires that you call `.finish()` to construct the middleware +- `Cors` builder now requires that you call `.finish()` to construct the middleware ## 1.0.1 -* Cors middleware has been moved to `actix-cors` crate +- Cors middleware has been moved to `actix-cors` crate instead of @@ -144,7 +144,7 @@ use actix_cors::Cors; ``` -* Identity middleware has been moved to `actix-identity` crate +- Identity middleware has been moved to `actix-identity` crate instead of @@ -161,7 +161,7 @@ ## 1.0.0 -* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration +- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration instead of @@ -219,7 +219,7 @@ ) ``` -* Resource registration. 1.0 version uses generalized resource +- Resource registration. 1.0 version uses generalized resource registration via `.service()` method. instead of @@ -239,7 +239,7 @@ .route(web::post().to(post_handler)) ``` -* Scope registration. +- Scope registration. instead of @@ -263,7 +263,7 @@ ); ``` -* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. +- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. instead of @@ -277,7 +277,7 @@ App.new().service(web::resource("/welcome").to(welcome)) ``` -* Passing arguments to handler with extractors, multiple arguments are allowed +- Passing arguments to handler with extractors, multiple arguments are allowed instead of @@ -295,7 +295,7 @@ } ``` -* `.f()`, `.a()` and `.h()` handler registration methods have been removed. +- `.f()`, `.a()` and `.h()` handler registration methods have been removed. Use `.to()` for handlers and `.to_async()` for async handlers. Handler function must use extractors. @@ -311,7 +311,7 @@ App.new().service(web::resource("/welcome").to(welcome)) ``` -* `HttpRequest` does not provide access to request's payload stream. +- `HttpRequest` does not provide access to request's payload stream. instead of @@ -341,7 +341,7 @@ } ``` -* `State` is now `Data`. You register Data during the App initialization process +- `State` is now `Data`. You register Data during the App initialization process and then access it from handlers either using a Data extractor or using HttpRequest's api. @@ -377,7 +377,7 @@ ``` -* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. +- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. instead of @@ -393,7 +393,7 @@ .. simply omit AsyncResponder and the corresponding responder() finish method -* Middleware +- Middleware instead of @@ -410,7 +410,7 @@ .route("/index.html", web::get().to(index)); ``` -* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` +- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. instead of @@ -432,9 +432,9 @@ } ``` -* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type +- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type -* StaticFiles and NamedFile have been moved to a separate crate. +- StaticFiles and NamedFile have been moved to a separate crate. instead of `use actix_web::fs::StaticFile` @@ -444,20 +444,20 @@ use `use actix_files::NamedFile` -* Multipart has been moved to a separate crate. +- Multipart has been moved to a separate crate. instead of `use actix_web::multipart::Multipart` use `use actix_multipart::Multipart` -* Response compression is not enabled by default. +- Response compression is not enabled by default. To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. -* Session middleware moved to actix-session crate +- Session middleware moved to actix-session crate -* Actors support have been moved to `actix-web-actors` crate +- Actors support have been moved to `actix-web-actors` crate -* Custom Error +- Custom Error Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. @@ -471,7 +471,7 @@ ## 0.7.15 -* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in +- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in your routes, you should use `%20`. instead of @@ -496,18 +496,18 @@ } ``` -* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` +- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` ## 0.7.4 -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple +- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple even for handler with one parameter. ## 0.7 -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload +- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload use `HttpMessage::payload()` method. instead of @@ -533,10 +533,10 @@ } ``` -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) +- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) trait uses `&HttpRequest` instead of `&mut HttpRequest`. -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. +- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. instead of @@ -550,17 +550,17 @@ fn index((query, json): (Query<..>, Json impl Responder {} ``` -* `Handler::handle()` uses `&self` instead of `&mut self` +- `Handler::handle()` uses `&self` instead of `&mut self` -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value +- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value -* Removed deprecated `HttpServer::threads()`, use +- Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. -* Renamed `client::ClientConnectorError::Connector` to +- Renamed `client::ClientConnectorError::Connector` to `client::ClientConnectorError::Resolver` -* `Route::with()` does not return `ExtractorConfig`, to configure +- `Route::with()` does not return `ExtractorConfig`, to configure extractor use `Route::with_config()` instead of @@ -589,26 +589,26 @@ } ``` -* `Route::with_async()` does not return `ExtractorConfig`, to configure +- `Route::with_async()` does not return `ExtractorConfig`, to configure extractor use `Route::with_async_config()` ## 0.6 -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` +- `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` -* `ws::Message::Close` now includes optional close reason. +- `ws::Message::Close` now includes optional close reason. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. -* `HttpServer::threads()` renamed to `HttpServer::workers()`. +- `HttpServer::threads()` renamed to `HttpServer::workers()`. -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. +- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. -* `HttpRequest::extensions()` returns read only reference to the request's Extension +- `HttpRequest::extensions()` returns read only reference to the request's Extension `HttpRequest::extensions_mut()` returns mutable reference. -* Instead of +- Instead of `use actix_web::middleware::{ CookieSessionBackend, CookieSessionError, RequestSession, @@ -619,15 +619,15 @@ `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` -* `FromRequest::from_request()` accepts mutable reference to a request +- `FromRequest::from_request()` accepts mutable reference to a request -* `FromRequest::Result` has to implement `Into>` +- `FromRequest::Result` has to implement `Into>` -* [`Responder::respond_to()`]( +- [`Responder::respond_to()`]( https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) is generic over `S` -* Use `Query` extractor instead of HttpRequest::query()`. +- Use `Query` extractor instead of HttpRequest::query()`. ```rust fn index(q: Query>) -> Result<..> { @@ -641,37 +641,37 @@ let q = Query::>::extract(req); ``` -* Websocket operations are implemented as `WsWriter` trait. +- Websocket operations are implemented as `WsWriter` trait. you need to use `use actix_web::ws::WsWriter` ## 0.5 -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` +- `HttpResponseBuilder::body()`, `.finish()`, `.json()` methods return `HttpResponse` instead of `Result` -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` +- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` moved to `actix_web::http` module -* `actix_web::header` moved to `actix_web::http::header` +- `actix_web::header` moved to `actix_web::http::header` -* `NormalizePath` moved to `actix_web::http` module +- `NormalizePath` moved to `actix_web::http` module -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, +- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, shortcut for `actix_web::server::HttpServer::new()` -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself +- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. +- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type +- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` +- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` functions should be used instead -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` +- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` instead of `Result<_, http::Error>` -* `Application` renamed to a `App` +- `Application` renamed to a `App` -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` +- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/README.md b/README.md index 5cce9f3b9..a9afbf386 100644 --- a/README.md +++ b/README.md @@ -21,25 +21,25 @@ ## Features -* Supports *HTTP/1.x* and *HTTP/2* -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate, zstd) -* Powerful [request routing](https://actix.rs/docs/url-dispatch/) -* Multipart streams -* Static assets -* SSL support using OpenSSL or Rustls -* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -* Includes an async [HTTP client](https://docs.rs/awc/) -* Runs on stable Rust 1.52+ +- Supports *HTTP/1.x* and *HTTP/2* +- Streaming and pipelining +- Keep-alive and slow requests handling +- Client/server [WebSockets](https://actix.rs/docs/websockets/) support +- Transparent content compression/decompression (br, gzip, deflate, zstd) +- Powerful [request routing](https://actix.rs/docs/url-dispatch/) +- Multipart streams +- Static assets +- SSL support using OpenSSL or Rustls +- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) +- Includes an async [HTTP client](https://docs.rs/awc/) +- Runs on stable Rust 1.52+ ## Documentation -* [Website & User Guide](https://actix.rs) -* [Examples Repository](https://github.com/actix/examples) -* [API Documentation](https://docs.rs/actix-web) -* [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) +- [Website & User Guide](https://actix.rs) +- [Examples Repository](https://github.com/actix/examples) +- [API Documentation](https://docs.rs/actix-web) +- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) ## Example @@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> { ### More examples -* [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) -* [Application State](https://github.com/actix/examples/tree/master/basics/state/) -* [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) -* [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) -* [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) -* [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) -* [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) -* [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) -* [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) -* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) -* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) -* [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) +- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) +- [Application State](https://github.com/actix/examples/tree/master/basics/state/) +- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) +- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) +- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) +- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) +- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) +- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) +- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) +- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) +- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) +- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. @@ -96,9 +96,9 @@ One of the fastest web frameworks available according to the This project is licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) at your option. diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index d6b39e28f..ef8eba0fc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,42 +4,42 @@ ## 0.6.0-beta.10 - 2021-12-11 -* No significant changes since `0.6.0-beta.9`. +- No significant changes since `0.6.0-beta.9`. ## 0.6.0-beta.9 - 2021-11-22 -* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] -* Add `NamedFile::open_async`. [#2408] -* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] -* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] -* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] -* Add `impl Clone` for `FilesService`. [#2408] +- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408] +- Add `NamedFile::open_async`. [#2408] +- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453] +- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408] +- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408] +- Add `impl Clone` for `FilesService`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 [#2453]: https://github.com/actix/actix-web/pull/2453 ## 0.6.0-beta.8 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.6.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.6.0-beta.6 - 2021-06-26 -* Added `Files::path_filter()`. [#2274] -* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] +- Added `Files::path_filter()`. [#2274] +- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228] [#2274]: https://github.com/actix/actix-web/pull/2274 [#2228]: https://github.com/actix/actix-web/pull/2228 ## 0.6.0-beta.5 - 2021-06-17 -* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] -* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] -* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] -* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] +- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135] +- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156] +- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225] +- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257] [#2135]: https://github.com/actix/actix-web/pull/2135 [#2156]: https://github.com/actix/actix-web/pull/2156 @@ -48,130 +48,130 @@ ## 0.6.0-beta.4 - 2021-04-02 -* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] +- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] [#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.6.0-beta.2 - 2021-02-10 -* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] -* Replace `v_htmlescape` with `askama_escape`. [#1953] +- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +- Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 -* `HttpRange::parse` now has its own error type. -* Update `bytes` to `1.0`. [#1813] +- `HttpRange::parse` now has its own error type. +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 -* Optionally support hidden files/directories. [#1811] +- Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 -* Clarify order of parameters in `Files::new` and improve docs. +- Clarify order of parameters in `Files::new` and improve docs. ## 0.4.0 - 2020-10-06 -* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] +- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 ## 0.3.0 - 2020-09-11 -* No significant changes from 0.3.0-beta.1. +- No significant changes from 0.3.0-beta.1. ## 0.3.0-beta.1 - 2020-07-15 -* Update `v_htmlescape` to 0.10 -* Update `actix-web` and `actix-http` dependencies to beta.1 +- Update `v_htmlescape` to 0.10 +- Update `actix-web` and `actix-http` dependencies to beta.1 ## 0.3.0-alpha.1 - 2020-05-23 -* Update `actix-web` and `actix-http` dependencies to alpha -* Fix some typos in the docs -* Bump minimum supported Rust version to 1.40 -* Support sending Content-Length when Content-Range is specified [#1384] +- Update `actix-web` and `actix-http` dependencies to alpha +- Fix some typos in the docs +- Bump minimum supported Rust version to 1.40 +- Support sending Content-Length when Content-Range is specified [#1384] [#1384]: https://github.com/actix/actix-web/pull/1384 ## 0.2.1 - 2019-12-22 -* Use the same format for file URLs regardless of platforms +- Use the same format for file URLs regardless of platforms ## 0.2.0 - 2019-12-20 -* Fix BodyEncoding trait import #1220 +- Fix BodyEncoding trait import #1220 ## 0.2.0-alpha.1 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.1.7 - 2019-11-06 -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ## 0.1.6 - 2019-10-14 -* Add option to redirect to a slash-ended path `Files` #1132 +- Add option to redirect to a slash-ended path `Files` #1132 ## 0.1.5 - 2019-10-08 -* Bump up `mime_guess` crate version to 2.0.1 -* Bump up `percent-encoding` crate version to 2.1 -* Allow user defined request guards for `Files` #1113 +- Bump up `mime_guess` crate version to 2.0.1 +- Bump up `percent-encoding` crate version to 2.1 +- Allow user defined request guards for `Files` #1113 ## 0.1.4 - 2019-07-20 -* Allow to disable `Content-Disposition` header #686 +- Allow to disable `Content-Disposition` header #686 ## 0.1.3 - 2019-06-28 -* Do not set `Content-Length` header, let actix-http set it #930 +- Do not set `Content-Length` header, let actix-http set it #930 ## 0.1.2 - 2019-06-13 -* Content-Length is 0 for NamedFile HEAD request #914 -* Fix ring dependency from actix-web default features for #741 +- Content-Length is 0 for NamedFile HEAD request #914 +- Fix ring dependency from actix-web default features for #741 ## 0.1.1 - 2019-06-01 -* Static files are incorrectly served as both chunked and with length #812 +- Static files are incorrectly served as both chunked and with length #812 ## 0.1.0 - 2019-05-25 -* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 +- NamedFile last-modified check always fails due to nano-seconds in file modified date #820 ## 0.1.0-beta.4 - 2019-05-12 -* Update actix-web to beta.4 +- Update actix-web to beta.4 ## 0.1.0-beta.1 - 2019-04-20 -* Update actix-web to beta.1 +- Update actix-web to beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Update actix-web to alpha6 +- Update actix-web to alpha6 ## 0.1.0-alpha.4 - 2019-04-08 -* Update actix-web to alpha4 +- Update actix-web to alpha4 ## 0.1.0-alpha.2 - 2019-04-02 -* Add default handler support +- Add default handler support ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 156012168..4e86e20e8 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -4,125 +4,125 @@ ## 3.0.0-beta.9 - 2021-12-11 -* No significant changes since `3.0.0-beta.8`. +- No significant changes since `3.0.0-beta.8`. ## 3.0.0-beta.8 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 3.0.0-beta.6 - 2021-11-15 -* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] -* Update `actix-server` to `2.0.0-beta.9`. [#2442] -* Minimum supported Rust version (MSRV) is now 1.52. +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Minimum supported Rust version (MSRV) is now 1.52. [#2442]: https://github.com/actix/actix-web/pull/2442 ## 3.0.0-beta.5 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 3.0.0-beta.4 - 2021-04-02 -* Added `TestServer::client_headers` method. [#2097] +- Added `TestServer::client_headers` method. [#2097] [#2097]: https://github.com/actix/actix-web/pull/2097 ## 3.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.1 - 2021-01-07 -* Update `bytes` to `1.0`. [#1813] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 -* Add ability to set address for `TestServer`. [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Add ability to set address for `TestServer`. [#1645] +- Upgrade `base64` to `0.13`. +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 ## 2.0.0 - 2020-09-11 -* Update actix-codec and actix-utils dependencies. +- Update actix-codec and actix-utils dependencies. ## 2.0.0-alpha.1 - 2020-05-23 -* Update the `time` dependency to 0.2.7 -* Update `actix-connect` dependency to 2.0.0-alpha.2 -* Make `test_server` `async` fn. -* Bump minimum supported Rust version to 1.40 -* Replace deprecated `net2` crate with `socket2` -* Update `base64` dependency to 0.12 -* Update `env_logger` dependency to 0.7 +- Update the `time` dependency to 0.2.7 +- Update `actix-connect` dependency to 2.0.0-alpha.2 +- Make `test_server` `async` fn. +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` +- Update `base64` dependency to 0.12 +- Update `env_logger` dependency to 0.7 ## 1.0.0 - 2019-12-13 -* Replaced `TestServer::start()` with `test_server()` +- Replaced `TestServer::start()` with `test_server()` ## 1.0.0-alpha.3 - 2019-12-07 -* Migrate to `std::future` +- Migrate to `std::future` ## 0.2.5 - 2019-09-17 -* Update serde_urlencoded to "0.6.1" -* Increase TestServerRuntime timeouts from 500ms to 3000ms -* Do not override current `System` +- Update serde_urlencoded to "0.6.1" +- Increase TestServerRuntime timeouts from 500ms to 3000ms +- Do not override current `System` ## 0.2.4 - 2019-07-18 -* Update actix-server to 0.6 +- Update actix-server to 0.6 ## 0.2.3 - 2019-07-16 -* Add `delete`, `options`, `patch` methods to `TestServerRunner` +- Add `delete`, `options`, `patch` methods to `TestServerRunner` ## 0.2.2 - 2019-06-16 -* Add .put() and .sput() methods +- Add .put() and .sput() methods ## 0.2.1 - 2019-06-05 -* Add license files +- Add license files ## 0.2.0 - 2019-05-12 -* Update awc and actix-http deps +- Update awc and actix-http deps ## 0.1.1 - 2019-04-24 -* Always make new connection for http client +- Always make new connection for http client ## 0.1.0 - 2019-04-16 -* No changes +- No changes ## 0.1.0-alpha.3 - 2019-04-02 -* Request functions accept path #743 +- Request functions accept path #743 ## 0.1.0-alpha.2 - 2019-03-29 -* Added TestServerRuntime::load_body() method -* Update actix-http and awc libraries +- Added TestServerRuntime::load_body() method +- Update actix-http and awc libraries ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ad98d132a..3b45e934f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,22 +2,22 @@ ## Unreleased - 2021-xx-xx ### Changes -* `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] [#2527]: https://github.com/actix/actix-web/pull/2527 ## 3.0.0-beta.16 - 2021-12-17 ### Added -* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] +- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522] ### Changed -* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] -* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] -* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] +- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510] +- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510] +- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510] ### Removed -* `MessageBody::{is_complete_body,take_complete_body}`. [#2522] +- `MessageBody::{is_complete_body,take_complete_body}`. [#2522] [#2510]: https://github.com/actix/actix-web/pull/2510 [#2522]: https://github.com/actix/actix-web/pull/2522 @@ -25,43 +25,43 @@ ## 3.0.0-beta.15 - 2021-12-11 ### Added -* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] -* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] -* `Response::map_into_boxed_body`. [#2468] -* `body::EitherBody` enum. [#2468] -* `body::None` struct. [#2468] -* Impl `MessageBody` for `bytestring::ByteString`. [#2468] -* `impl Clone for ws::HandshakeError`. [#2468] -* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] -* `impl Default ` for `ws::Codec`. [#1920] -* `header::QualityItem::{max, min}`. [#2486] -* `header::Quality::{MAX, MIN}`. [#2486] -* `impl Display` for `header::Quality`. [#2486] -* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] -* `Request::take_conn_data()`. [#2491] -* `Request::take_req_data()`. [#2487] -* `impl Clone` for `RequestHead`. [#2487] -* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] -* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] +- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] +- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] +- `Response::map_into_boxed_body`. [#2468] +- `body::EitherBody` enum. [#2468] +- `body::None` struct. [#2468] +- Impl `MessageBody` for `bytestring::ByteString`. [#2468] +- `impl Clone for ws::HandshakeError`. [#2468] +- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] +- `impl Default ` for `ws::Codec`. [#1920] +- `header::QualityItem::{max, min}`. [#2486] +- `header::Quality::{MAX, MIN}`. [#2486] +- `impl Display` for `header::Quality`. [#2486] +- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] +- `Request::take_conn_data()`. [#2491] +- `Request::take_req_data()`. [#2487] +- `impl Clone` for `RequestHead`. [#2487] +- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497] +- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520] ### Changed -* Rename `body::BoxBody::{from_body => new}`. [#2468] -* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] -* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] -* Error types using in service builders now require `Into>`. [#2468] -* `From` implementations on error types now return a `Response`. [#2468] -* `ResponseBuilder::body(B)` now returns `Response>`. [#2468] -* `ResponseBuilder::finish()` now returns `Response>`. [#2468] +- Rename `body::BoxBody::{from_body => new}`. [#2468] +- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468] +- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468] +- Error types using in service builders now require `Into>`. [#2468] +- `From` implementations on error types now return a `Response`. [#2468] +- `ResponseBuilder::body(B)` now returns `Response>`. [#2468] +- `ResponseBuilder::finish()` now returns `Response>`. [#2468] ### Removed -* `ResponseBuilder::streaming`. [#2468] -* `impl Future` for `ResponseBuilder`. [#2468] -* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] -* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] -* `impl Copy` for `ws::Codec`. [#1920] -* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] -* `impl TryFrom` for `header::Quality`. [#2486] -* `http` module. Most everything it contained is exported at the crate root. [#2488] +- `ResponseBuilder::streaming`. [#2468] +- `impl Future` for `ResponseBuilder`. [#2468] +- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] +- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] +- `impl Copy` for `ws::Codec`. [#1920] +- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +- `impl TryFrom` for `header::Quality`. [#2486] +- `http` module. Most everything it contained is exported at the crate root. [#2488] [#2483]: https://github.com/actix/actix-web/pull/2483 [#2468]: https://github.com/actix/actix-web/pull/2468 @@ -76,10 +76,10 @@ ## 3.0.0-beta.14 - 2021-11-30 ### Changed -* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] -* Expose `header::map` module. [#2467] -* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] +- Expose `header::map` module. [#2467] +- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2467]: https://github.com/actix/actix-web/pull/2467 [#2470]: https://github.com/actix/actix-web/pull/2470 @@ -88,24 +88,24 @@ ## 3.0.0-beta.13 - 2021-11-22 ### Added -* `body::AnyBody::empty` for quickly creating an empty body. [#2446] -* `body::AnyBody::none` for quickly creating a "none" body. [#2456] -* `impl Clone` for `body::AnyBody where S: Clone`. [#2448] -* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] +- `body::AnyBody::empty` for quickly creating an empty body. [#2446] +- `body::AnyBody::none` for quickly creating a "none" body. [#2456] +- `impl Clone` for `body::AnyBody where S: Clone`. [#2448] +- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448] ### Changed -* Rename `body::AnyBody::{Message => Body}`. [#2446] -* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] -* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] -* Rename `body::{BoxAnyBody => BoxBody}`. [#2448] -* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] -* `Encoder::response` now returns `AnyBody>`. [#2448] +- Rename `body::AnyBody::{Message => Body}`. [#2446] +- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448] +- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448] +- Rename `body::{BoxAnyBody => BoxBody}`. [#2448] +- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448] +- `Encoder::response` now returns `AnyBody>`. [#2448] ### Removed -* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] -* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] -* `EncoderError::Boxed`; it is no longer required. [#2446] -* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] +- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446] +- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] +- `EncoderError::Boxed`; it is no longer required. [#2446] +- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446] [#2446]: https://github.com/actix/actix-web/pull/2446 [#2448]: https://github.com/actix/actix-web/pull/2448 @@ -114,11 +114,11 @@ ## 3.0.0-beta.12 - 2021-11-15 ### Changed -* Update `actix-server` to `2.0.0-beta.9`. [#2442] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] ### Removed -* `client` module. [#2425] -* `trust-dns` feature. [#2425] +- `client` module. [#2425] +- `trust-dns` feature. [#2425] [#2425]: https://github.com/actix/actix-web/pull/2425 [#2442]: https://github.com/actix/actix-web/pull/2442 @@ -126,21 +126,21 @@ ## 3.0.0-beta.11 - 2021-10-20 ### Changed -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.10 - 2021-09-09 ### Changed -* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] -* Minimum supported Rust version (MSRV) is now 1.51. +- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] +- Minimum supported Rust version (MSRV) is now 1.51. ### Fixed -* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] -* Remove `Into` bound on `Encoder` body types. [#2375] -* Fix quality parse error in Accept-Encoding header. [#2344] +- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +- Remove `Into` bound on `Encoder` body types. [#2375] +- Fix quality parse error in Accept-Encoding header. [#2344] [#2364]: https://github.com/actix/actix-web/pull/2364 [#2375]: https://github.com/actix/actix-web/pull/2375 @@ -150,15 +150,15 @@ ## 3.0.0-beta.9 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 3.0.0-beta.8 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] ### Removed -* `downcast` and `downcast_get_type_id` macros. [#2291] +- `downcast` and `downcast_get_type_id` macros. [#2291] [#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 @@ -166,37 +166,37 @@ ## 3.0.0-beta.7 - 2021-06-17 ### Added -* Alias `body::Body` as `body::AnyBody`. [#2215] -* `BoxAnyBody`: a boxed message body with boxed errors. [#2183] -* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] -* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] -* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] -* `Response::into_body` that consumes response and returns body type. [#2201] -* `impl Default` for `Response`. [#2201] -* Add zstd support for `ContentEncoding`. [#2244] +- Alias `body::Body` as `body::AnyBody`. [#2215] +- `BoxAnyBody`: a boxed message body with boxed errors. [#2183] +- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] +- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] +- `Response::into_body` that consumes response and returns body type. [#2201] +- `impl Default` for `Response`. [#2201] +- Add zstd support for `ContentEncoding`. [#2244] ### Changed -* The `MessageBody` trait now has an associated `Error` type. [#2183] -* All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -* All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -* Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] -* `header` mod is now public. [#2171] -* `uri` mod is now public. [#2171] -* Update `language-tags` to `0.3`. -* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] -* `ResponseBuilder::message_body` now returns a `Result`. [#2201] -* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] -* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- The `MessageBody` trait now has an associated `Error` type. [#2183] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] +- `header` mod is now public. [#2171] +- `uri` mod is now public. [#2171] +- Update `language-tags` to `0.3`. +- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] +- `ResponseBuilder::message_body` now returns a `Result`. [#2201] +- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] ### Removed -* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] -* Down-casting for `MessageBody` types. [#2183] -* `error::Result` alias. [#2201] -* Error field from `Response` and `Response::error`. [#2205] -* `impl Future` for `Response`. [#2201] -* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] -* `InternalError` and all the error types it constructed. [#2215] -* Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] +- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] +- Down-casting for `MessageBody` types. [#2183] +- `error::Result` alias. [#2201] +- Error field from `Response` and `Response::error`. [#2205] +- `impl Future` for `Response`. [#2201] +- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201] +- `InternalError` and all the error types it constructed. [#2215] +- Conversion (`impl Into`) of `Response` and `ResponseBuilder` to `Error`. [#2215] [#2171]: https://github.com/actix/actix-web/pull/2171 [#2183]: https://github.com/actix/actix-web/pull/2183 @@ -211,27 +211,27 @@ ## 3.0.0-beta.6 - 2021-04-17 ### Added -* `impl MessageBody for Pin>`. [#2152] -* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] -* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] +- `impl MessageBody for Pin>`. [#2152] +- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] +- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] ### Changes -* The type parameter of `Response` no longer has a default. [#2152] -* The `Message` variant of `body::Body` is now `Pin>`. [#2152] -* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] -* Error enum types are marked `#[non_exhaustive]`. [#2161] +- The type parameter of `Response` no longer has a default. [#2152] +- The `Message` variant of `body::Body` is now `Pin>`. [#2152] +- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] +- Error enum types are marked `#[non_exhaustive]`. [#2161] ### Removed -* `cookies` feature flag. [#2065] -* Top-level `cookies` mod (re-export). [#2065] -* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] -* `impl ResponseError for CookieParseError`. [#2065] -* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] -* `ResponseBuilder::json`. [#2148] -* `ResponseBuilder::{set_header, header}`. [#2148] -* `impl From for Body`. [#2148] -* `Response::build_from`. [#2159] -* Most of the status code builders on `Response`. [#2159] +- `cookies` feature flag. [#2065] +- Top-level `cookies` mod (re-export). [#2065] +- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] +- `impl ResponseError for CookieParseError`. [#2065] +- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148] +- `ResponseBuilder::json`. [#2148] +- `ResponseBuilder::{set_header, header}`. [#2148] +- `impl From for Body`. [#2148] +- `Response::build_from`. [#2159] +- Most of the status code builders on `Response`. [#2159] [#2065]: https://github.com/actix/actix-web/pull/2065 [#2148]: https://github.com/actix/actix-web/pull/2148 @@ -243,16 +243,16 @@ ## 3.0.0-beta.5 - 2021-04-02 ### Added -* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] -* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] -* `client::ConnectionIo` trait alias [#2081] +- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] ### Changed -* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] +- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] ### Removed -* Common typed HTTP headers were moved to actix-web. [2094] -* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] +- Common typed HTTP headers were moved to actix-web. [2094] +- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127] [#2063]: https://github.com/actix/actix-web/pull/2063 [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -262,13 +262,13 @@ ## 3.0.0-beta.4 - 2021-03-08 ### Changed -* Feature `cookies` is now optional and disabled by default. [#1981] -* `ws::hash_key` now returns array. [#2035] -* `ResponseBuilder::json` now takes `impl Serialize`. [#2052] +- Feature `cookies` is now optional and disabled by default. [#1981] +- `ws::hash_key` now returns array. [#2035] +- `ResponseBuilder::json` now takes `impl Serialize`. [#2052] ### Removed -* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] -* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] +- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] [#1981]: https://github.com/actix/actix-web/pull/1981 [#1994]: https://github.com/actix/actix-web/pull/1994 @@ -277,48 +277,48 @@ ## 3.0.0-beta.3 - 2021-02-10 -* No notable changes. +- No notable changes. ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `TryIntoHeaderPair` 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] -* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] -* `HeaderMap::drain` as an efficient draining iterator. [#1964] -* Implement `IntoIterator` for owned `HeaderMap`. [#1964] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `TryIntoHeaderPair` 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] +- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +- `HeaderMap::drain` as an efficient draining iterator. [#1964] +- Implement `IntoIterator` for owned `HeaderMap`. [#1964] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed +- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] -* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std +- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] -* `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()`. [#1903] -* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] -* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool +- `Extensions::insert` returns Option of replaced item. [#1904] +- Remove `HttpResponseBuilder::json2()`. [#1903] +- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool is dead. [#1957] -* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] -* `HeaderMap::insert` now returns iterator of removed values. [#1964] -* `HeaderMap::remove` now returns iterator of removed values. [#1964] +- `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +- `HeaderMap::insert` now returns iterator of removed values. [#1964] +- `HeaderMap::remove` now returns iterator of removed values. [#1964] ### 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] -* `actors` optional feature. [#1969] -* `ResponseError` impl for `actix::MailboxError`. [#1969] +- `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] +- `actors` optional feature. [#1969] +- `ResponseError` impl for `actix::MailboxError`. [#1969] ### Documentation -* Vastly improve docs and add examples for `HeaderMap`. [#1964] +- Vastly improve docs and add examples for `HeaderMap`. [#1964] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -333,24 +333,24 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Added -* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. +- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. ### Changed -* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -* Bumped `rand` to `0.8`. -* Update `bytes` to `1.0`. [#1813] -* Update `h2` to `0.3`. [#1813] -* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `bytes` to `1.0`. [#1813] +- Update `h2` to `0.3`. [#1813] +- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed -* Deprecated `on_connect` methods have been removed. Prefer the new +- Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] -* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` +- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] -* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. +- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] -* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. +- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] @@ -362,20 +362,20 @@ ## 2.2.1 - 2021-08-09 ### Fixed -* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) +- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) ## 2.2.0 - 2020-11-25 ### Added -* HttpResponse builders for 1xx status codes. [#1768] -* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] -* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] +- HttpResponse builders for 1xx status codes. [#1768] +- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +- `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed -* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 @@ -386,12 +386,12 @@ ## 2.1.0 - 2020-10-30 ### Added -* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] +- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Upgrade `pin-project` to `1.0`. [#1733] -* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Upgrade `pin-project` to `1.0`. [#1733] +- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 [#1754]: https://github.com/actix/actix-web/pull/1754 @@ -400,28 +400,28 @@ ## 2.0.0 - 2020-09-11 -* No significant changes from `2.0.0-beta.4`. +- No significant changes from `2.0.0-beta.4`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec and actix-utils dependencies. -* Update actix-connect and actix-tls dependencies. +- Update actix-codec and actix-utils dependencies. +- Update actix-connect and actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-14 ### Fixed -* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] +- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 ## 2.0.0-beta.2 - 2020-07-21 ### Fixed -* Potential UB in h1 decoder using uninitialized memory. [#1614] +- Potential UB in h1 decoder using uninitialized memory. [#1614] ### Changed -* Fix illegal chunked encoding. [#1615] +- Fix illegal chunked encoding. [#1615] [#1614]: https://github.com/actix/actix-web/pull/1614 [#1615]: https://github.com/actix/actix-web/pull/1615 @@ -429,10 +429,10 @@ ## 2.0.0-beta.1 - 2020-07-11 ### Changed -* Migrate cookie handling to `cookie` crate. [#1558] -* Update `sha-1` to 0.9. [#1586] -* Fix leak in client pool. [#1580] -* MSRV is now 1.41.1. +- Migrate cookie handling to `cookie` crate. [#1558] +- Update `sha-1` to 0.9. [#1586] +- Fix leak in client pool. [#1580] +- MSRV is now 1.41.1. [#1558]: https://github.com/actix/actix-web/pull/1558 [#1586]: https://github.com/actix/actix-web/pull/1586 @@ -441,15 +441,15 @@ ## 2.0.0-alpha.4 - 2020-05-21 ### Changed -* Bump minimum supported Rust version to 1.40 -* content_length function is removed, and you can set Content-Length by calling +- Bump minimum supported Rust version to 1.40 +- content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] -* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. -* Update `base64` dependency to 0.12 +- Update `base64` dependency to 0.12 ### Fixed -* Support parsing of `SameSite=None` [#1503] +- Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 @@ -457,13 +457,13 @@ ## 2.0.0-alpha.3 - 2020-05-08 ### Fixed -* Correct spelling of ConnectError::Unresolved [#1487] -* Fix a mistake in the encoding of websocket continuation messages wherein +- Correct spelling of ConnectError::Unresolved [#1487] +- Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Remove `failure` support for `ResponseError` since that crate +- Implement `std::error::Error` for our custom errors [#1422] +- Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. [#1422]: https://github.com/actix/actix-web/pull/1422 @@ -472,12 +472,12 @@ ## 2.0.0-alpha.2 - 2020-03-07 ### Changed -* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] -* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB +- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] +- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively to improve download speed for awc when downloading large objects. [#1394] -* client::Connector accepts initial_window_size and initial_connection_window_size +- client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] -* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] +- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 @@ -485,61 +485,61 @@ ## 2.0.0-alpha.1 - 2020-02-27 ### Changed -* Update the `time` dependency to 0.2.7. -* Moved actors messages support from actix crate, enabled with feature `actors`. -* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of +- Update the `time` dependency to 0.2.7. +- Moved actors messages support from actix crate, enabled with feature `actors`. +- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of `&mut self` in the poll_next(). -* MessageBody is not implemented for &'static [u8] anymore. +- MessageBody is not implemented for &'static [u8] anymore. ### Fixed -* Allow `SameSite=None` cookies to be sent in a response. +- Allow `SameSite=None` cookies to be sent in a response. ## 1.0.1 - 2019-12-20 ### Fixed -* Poll upgrade service's readiness from HTTP service handlers -* Replace brotli with brotli2 #1224 +- Poll upgrade service's readiness from HTTP service handlers +- Replace brotli with brotli2 #1224 ## 1.0.0 - 2019-12-13 ### Added -* Add websockets continuation frame support +- Add websockets continuation frame support ### Changed -* Replace `flate2-xxx` features with `compress` +- Replace `flate2-xxx` features with `compress` ## 1.0.0-alpha.5 - 2019-12-09 ### Fixed -* Check `Upgrade` service readiness before calling it -* Fix buffer remaining capacity calculation +- Check `Upgrade` service readiness before calling it +- Fix buffer remaining capacity calculation ### Changed -* Websockets: Ping and Pong should have binary data #1049 +- Websockets: Ping and Pong should have binary data #1049 ## 1.0.0-alpha.4 - 2019-12-08 ### Added -* Add impl ResponseBuilder for Error +- Add impl ResponseBuilder for Error ### Changed -* Use rust based brotli compression library +- Use rust based brotli compression library ## 1.0.0-alpha.3 - 2019-12-07 ### Changed -* Migrate to tokio 0.2 -* Migrate to `std::future` +- Migrate to tokio 0.2 +- Migrate to `std::future` ## 0.2.11 - 2019-11-06 ### Added -* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -* Add an additional `filename*` param in the `Content-Disposition` header of +- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` +- Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; +- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header [#1118] [#1878]: https://github.com/actix/actix-web/pull/1878 @@ -547,169 +547,169 @@ ## 0.2.10 - 2019-09-11 ### Added -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests +- Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` ### Fixed -* h2 will use error response #1080 -* on_connect result isn't added to request extensions for http2 requests #1009 +- h2 will use error response #1080 +- on_connect result isn't added to request extensions for http2 requests #1009 ## 0.2.9 - 2019-08-13 ### Changed -* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation -* Update percent-encoding to 2.1 -* Update serde_urlencoded to 0.6.1 +- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +- Update percent-encoding to 2.1 +- Update serde_urlencoded to 0.6.1 ### Fixed -* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) +- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) ## 0.2.8 - 2019-08-01 ### Added -* Add `rustls` support -* Add `Clone` impl for `HeaderMap` +- Add `rustls` support +- Add `Clone` impl for `HeaderMap` ### Fixed -* awc client panic #1016 -* Invalid response with compression middleware enabled, but compression-related features +- awc client panic #1016 +- Invalid response with compression middleware enabled, but compression-related features disabled #997 ## 0.2.7 - 2019-07-18 ### Added -* Add support for downcasting response errors #986 +- Add support for downcasting response errors #986 ## 0.2.6 - 2019-07-17 ### Changed -* Replace `ClonableService` with local copy -* Upgrade `rand` dependency version to 0.7 +- Replace `ClonableService` with local copy +- Upgrade `rand` dependency version to 0.7 ## 0.2.5 - 2019-06-28 ### Added -* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 +- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate -* Add `Copy` and `Clone` impls for `ws::Codec` +- Use `encoding_rs` crate instead of unmaintained `encoding` crate +- Add `Copy` and `Clone` impls for `ws::Codec` ## 0.2.4 - 2019-06-16 ### Fixed -* Do not compress NoContent (204) responses #918 +- Do not compress NoContent (204) responses #918 ## 0.2.3 - 2019-06-02 ### Added -* Debug impl for ResponseBuilder -* From SizedStream and BodyStream for Body +- Debug impl for ResponseBuilder +- From SizedStream and BodyStream for Body ### Changed -* SizedStream uses u64 +- SizedStream uses u64 ## 0.2.2 - 2019-05-29 ### Fixed -* Parse incoming stream before closing stream on disconnect #868 +- Parse incoming stream before closing stream on disconnect #868 ## 0.2.1 - 2019-05-25 ### Fixed -* Handle socket read disconnect +- Handle socket read disconnect ## 0.2.0 - 2019-05-12 ### Changed -* Update actix-service to 0.4 -* Expect and upgrade services accept `ServerConfig` config. +- Update actix-service to 0.4 +- Expect and upgrade services accept `ServerConfig` config. ### Deleted -* `OneRequest` service +- `OneRequest` service ## 0.1.5 - 2019-05-04 ### Fixed -* Clean up response extensions in response pool #817 +- Clean up response extensions in response pool #817 ## 0.1.4 - 2019-04-24 ### Added -* Allow to render h1 request headers in `Camel-Case` +- Allow to render h1 request headers in `Camel-Case` ### Fixed -* Read until eof for http/1.0 responses #771 +- Read until eof for http/1.0 responses #771 ## 0.1.3 - 2019-04-23 ### Fixed -* Fix http client pool management -* Fix http client wait queue management #794 +- Fix http client pool management +- Fix http client wait queue management #794 ## 0.1.2 - 2019-04-23 ### Fixed -* Fix BorrowMutError panic in client connector #793 +- Fix BorrowMutError panic in client connector #793 ## 0.1.1 - 2019-04-19 ### Changed -* Cookie::max_age() accepts value in seconds -* Cookie::max_age_time() accepts value in time::Duration -* Allow to specify server address for client connector +- Cookie::max_age() accepts value in seconds +- Cookie::max_age_time() accepts value in time::Duration +- Allow to specify server address for client connector ## 0.1.0 - 2019-04-16 ### Added -* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` +- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed -* `actix_http::encoding` always available -* use trust-dns-resolver 0.11.0 +- `actix_http::encoding` always available +- use trust-dns-resolver 0.11.0 ## 0.1.0-alpha.5 - 2019-04-12 ### Added -* Allow to use custom service for upgrade requests -* Added `h1::SendResponse` future. +- Allow to use custom service for upgrade requests +- Added `h1::SendResponse` future. ### Changed -* MessageBody::length() renamed to MessageBody::size() for consistency -* ws handshake verification functions take RequestHead instead of Request +- MessageBody::length() renamed to MessageBody::size() for consistency +- ws handshake verification functions take RequestHead instead of Request ## 0.1.0-alpha.4 - 2019-04-08 ### Added -* Allow to use custom `Expect` handler -* Add minimal `std::error::Error` impl for `Error` +- Allow to use custom `Expect` handler +- Add minimal `std::error::Error` impl for `Error` ### Changed -* Export IntoHeaderValue -* Render error and return as response body -* Use thread pool for response body compression +- Export IntoHeaderValue +- Render error and return as response body +- Use thread pool for response body compression ### Deleted -* Removed PayloadBuffer +- Removed PayloadBuffer ## 0.1.0-alpha.3 - 2019-04-02 ### Added -* Warn when an unsealed private cookie isn't valid UTF-8 +- Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed -* Rust 1.31.0 compatibility -* Preallocate read buffer for h1 codec -* Detect socket disconnection during protocol selection +- Rust 1.31.0 compatibility +- Preallocate read buffer for h1 codec +- Detect socket disconnection during protocol selection ## 0.1.0-alpha.2 - 2019-03-29 ### Added -* Added ws::Message::Nop, no-op websockets message +- Added ws::Message::Nop, no-op websockets message ### Changed -* Do not use thread pool for decompression if chunk size is smaller than 2048. +- Do not use thread pool for decompression if chunk size is smaller than 2048. ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-http/README.md b/actix-http/README.md index 731d7a48e..05edffd2c 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -54,8 +54,8 @@ async fn main() -> io::Result<()> { This project is licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) at your option. diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 8d9c1640f..e58c3ee24 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -4,119 +4,119 @@ ## 0.4.0-beta.10 - 2021-12-11 -* No significant changes since `0.4.0-beta.9`. +- No significant changes since `0.4.0-beta.9`. ## 0.4.0-beta.9 - 2021-12-01 -* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] +- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] [#2463]: https://github.com/actix/actix-web/pull/2463 ## 0.4.0-beta.8 - 2021-11-22 -* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] -* Added `MultipartError::NoContentDisposition` variant. [#2451] -* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] -* Added `Field::name` method for getting the field name. [#2451] -* `MultipartError` now marks variants with inner errors as the source. [#2451] -* `MultipartError` is now marked as non-exhaustive. [#2451] +- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451] +- Added `MultipartError::NoContentDisposition` variant. [#2451] +- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451] +- Added `Field::name` method for getting the field name. [#2451] +- `MultipartError` now marks variants with inner errors as the source. [#2451] +- `MultipartError` is now marked as non-exhaustive. [#2451] [#2451]: https://github.com/actix/actix-web/pull/2451 ## 0.4.0-beta.7 - 2021-10-20 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.4.0-beta.6 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.4.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.4.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 0.4.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 0.4.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 0.4.0-beta.1 - 2021-01-07 -* Fix multipart consuming payload before header checks. [#1513] -* Update `bytes` to `1.0`. [#1813] +- Fix multipart consuming payload before header checks. [#1513] +- Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1513]: https://github.com/actix/actix-web/pull/1513 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.2`. +- No significant changes from `0.3.0-beta.2`. ## 0.3.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## 0.3.0-beta.1 - 2020-07-15 -* Update `actix-web` to 3.0.0-beta.1 +- Update `actix-web` to 3.0.0-beta.1 ## 0.3.0-alpha.1 - 2020-05-25 -* Update `actix-web` to 3.0.0-alpha.3 -* Bump minimum supported Rust version to 1.40 -* Minimize `futures` dependencies -* Remove the unused `time` dependency -* Fix missing `std::error::Error` implement for `MultipartError`. +- Update `actix-web` to 3.0.0-alpha.3 +- Bump minimum supported Rust version to 1.40 +- Minimize `futures` dependencies +- Remove the unused `time` dependency +- Fix missing `std::error::Error` implement for `MultipartError`. ## [0.2.0] - 2019-12-20 -* Release +- Release ## [0.2.0-alpha.4] - 2019-12-xx -* Multipart handling now handles Pending during read of boundary #1205 +- Multipart handling now handles Pending during read of boundary #1205 ## [0.2.0-alpha.2] - 2019-12-03 -* Migrate to `std::future` +- Migrate to `std::future` ## [0.1.4] - 2019-09-12 -* Multipart handling now parses requests which do not end in CRLF #1038 +- Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 -* Fix ring dependency from actix-web default features for #741. +- Fix ring dependency from actix-web default features for #741. ## [0.1.2] - 2019-06-02 -* Fix boundary parsing #876 +- Fix boundary parsing #876 ## [0.1.1] - 2019-05-25 -* Fix disconnect handling #834 +- Fix disconnect handling #834 ## [0.1.0] - 2019-05-18 -* Release +- Release ## [0.1.0-beta.4] - 2019-05-12 -* Handle cancellation of uploads #736 +- Handle cancellation of uploads #736 -* Upgrade to actix-web 1.0.0-beta.4 +- Upgrade to actix-web 1.0.0-beta.4 ## [0.1.0-beta.1] - 2019-04-21 -* Do not support nested multipart +- Do not support nested multipart -* Split multipart support to separate crate +- Split multipart support to separate crate -* Optimize multipart handling #634, #769 +- Optimize multipart handling #634, #769 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index d0ed55c88..0a6a56359 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -4,20 +4,20 @@ ## 0.5.0-beta.3 - 2021-12-17 -* Minimum supported Rust version (MSRV) is now 1.52. +- Minimum supported Rust version (MSRV) is now 1.52. ## 0.5.0-beta.2 - 2021-09-09 -* Introduce `ResourceDef::join`. [#380] -* Disallow prefix routes with tail segments. [#379] -* Enforce path separators on dynamic prefixes. [#378] -* Improve malformed path error message. [#384] -* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] -* Prefix segments with trailing slashes define a trailing empty segment. [#2355] -* Support multi-pattern prefixes and joins. [#2356] -* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -* Support `build_resource_path` on multi-pattern resources. [#2356] -* Minimum supported Rust version (MSRV) is now 1.51. +- Introduce `ResourceDef::join`. [#380] +- Disallow prefix routes with tail segments. [#379] +- Enforce path separators on dynamic prefixes. [#378] +- Improve malformed path error message. [#384] +- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +- Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- Support multi-pattern prefixes and joins. [#2356] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- Support `build_resource_path` on multi-pattern resources. [#2356] +- Minimum supported Rust version (MSRV) is now 1.51. [#378]: https://github.com/actix/actix-net/pull/378 [#379]: https://github.com/actix/actix-net/pull/379 @@ -28,23 +28,23 @@ ## 0.5.0-beta.1 - 2021-07-20 -* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -* Fix `ResourceDef` `PartialEq` implementation. [#373] -* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -* Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -* Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -* Rename `Router::{*_checked => *_fn}`. [#373] -* Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +- Fix `ResourceDef` `PartialEq` implementation. [#373] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] +- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] +- Rename `Router::{*_checked => *_fn}`. [#373] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] [#368]: https://github.com/actix/actix-net/pull/368 [#366]: https://github.com/actix/actix-net/pull/366 @@ -56,10 +56,10 @@ ## 0.4.0 - 2021-06-06 -* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -* Path tail patterns now match new lines (`\n`) in request URL. [#360] -* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -* Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] +- Path tail patterns now match new lines (`\n`) in request URL. [#360] +- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] +- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] [#345]: https://github.com/actix/actix-net/pull/345 [#357]: https://github.com/actix/actix-net/pull/357 @@ -68,68 +68,68 @@ ## 0.3.0 - 2019-12-31 -* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 +- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0 ## 0.2.7 - 2021-02-06 -* Add `Router::recognize_checked` [#247] +- Add `Router::recognize_checked` [#247] [#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -* Use `bytestring` version range compatible with Bytes v1.0. [#246] +- Use `bytestring` version range compatible with Bytes v1.0. [#246] [#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 -* Fix `from_hex()` method +- Fix `from_hex()` method ## 0.2.4 - 2019-12-31 -* Add `ResourceDef::resource_path_named()` path generation method +- Add `ResourceDef::resource_path_named()` path generation method ## 0.2.3 - 2019-12-25 -* Add impl `IntoPattern` for `&String` +- Add impl `IntoPattern` for `&String` ## 0.2.2 - 2019-12-25 -* Use `IntoPattern` for `RouterBuilder::path()` +- Use `IntoPattern` for `RouterBuilder::path()` ## 0.2.1 - 2019-12-25 -* Add `IntoPattern` trait -* Add multi-pattern resources +- Add `IntoPattern` trait +- Add multi-pattern resources ## 0.2.0 - 2019-12-07 -* Update http to 0.2 -* Update regex to 1.3 -* Use bytestring instead of string +- Update http to 0.2 +- Update regex to 1.3 +- Use bytestring instead of string ## 0.1.5 - 2019-05-15 -* Remove debug prints +- Remove debug prints ## 0.1.4 - 2019-05-15 -* Fix checked resource match +- Fix checked resource match ## 0.1.3 - 2019-04-22 -* Added support for `remainder match` (i.e "/path/{tail}*") +- Added support for `remainder match` (i.e "/path/{tail}*") ## 0.1.2 - 2019-04-07 -* Export `Quoter` type -* Allow to reset `Path` instance +- Export `Quoter` type +- Allow to reset `Path` instance ## 0.1.1 - 2019-04-03 -* Get dynamic segment by name instead of iterator. +- Get dynamic segment by name instead of iterator. ## 0.1.0 - 2019-03-09 -* Initial release +- Initial release diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index ef78ac54a..e3deeb3f4 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -4,46 +4,46 @@ ## 0.1.0-beta.9 - 2021-12-17 -* Re-export `actix_http::body::to_bytes`. [#2518] -* Update `actix_web::test` re-exports. [#2518] +- Re-export `actix_http::body::to_bytes`. [#2518] +- Update `actix_web::test` re-exports. [#2518] [#2518]: https://github.com/actix/actix-web/pull/2518 ## 0.1.0-beta.8 - 2021-12-11 -* No significant changes since `0.1.0-beta.7`. +- No significant changes since `0.1.0-beta.7`. ## 0.1.0-beta.7 - 2021-11-22 -* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] +- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408] [#2408]: https://github.com/actix/actix-web/pull/2408 ## 0.1.0-beta.6 - 2021-11-15 -* No significant changes from `0.1.0-beta.5`. +- No significant changes from `0.1.0-beta.5`. ## 0.1.0-beta.5 - 2021-10-20 -* Updated rustls to v0.20. [#2414] -* Minimum supported Rust version (MSRV) is now 1.52. +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. [#2414]: https://github.com/actix/actix-web/pull/2414 ## 0.1.0-beta.4 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 0.1.0-beta.3 - 2021-06-20 -* No significant changes from `0.1.0-beta.2`. +- No significant changes from `0.1.0-beta.2`. ## 0.1.0-beta.2 - 2021-04-17 -* No significant changes from `0.1.0-beta.1`. +- No significant changes from `0.1.0-beta.1`. ## 0.1.0-beta.1 - 2021-04-02 -* Move integration testing structs from `actix-web`. [#2112] +- Move integration testing structs from `actix-web`. [#2112] [#2112]: https://github.com/actix/actix-web/pull/2112 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index d3078499c..6abfe2c61 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,105 +4,105 @@ ## 4.0.0-beta.8 - 2021-12-11 -* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] -* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] -* Minimum supported Rust version (MSRV) is now 1.52. +- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] +- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] +- Minimum supported Rust version (MSRV) is now 1.52. [#1920]: https://github.com/actix/actix-web/pull/1920 ## 4.0.0-beta.7 - 2021-09-09 -* Minimum supported Rust version (MSRV) is now 1.51. +- Minimum supported Rust version (MSRV) is now 1.51. ## 4.0.0-beta.6 - 2021-06-26 -* Update `actix` to `0.12`. [#2277] +- Update `actix` to `0.12`. [#2277] [#2277]: https://github.com/actix/actix-web/pull/2277 ## 4.0.0-beta.5 - 2021-06-17 -* No notable changes. +- No notable changes. ## 4.0.0-beta.4 - 2021-04-02 -* No notable changes. +- No notable changes. ## 4.0.0-beta.3 - 2021-03-09 -* No notable changes. +- No notable changes. ## 4.0.0-beta.2 - 2021-02-10 -* No notable changes. +- No notable changes. ## 4.0.0-beta.1 - 2021-01-07 -* Update `pin-project` to `1.0`. -* Update `bytes` to `1.0`. [#1813] -* `WebsocketContext::text` now takes an `Into`. [#1864] +- Update `pin-project` to `1.0`. +- Update `bytes` to `1.0`. [#1813] +- `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.2`. +- No significant changes from `3.0.0-beta.2`. ## 3.0.0-beta.2 - 2020-09-10 -* Update `actix-*` dependencies to latest versions. +- Update `actix-*` dependencies to latest versions. ## [3.0.0-beta.1] - 2020-xx-xx -* Update `actix-web` & `actix-http` dependencies to beta.1 -* Bump minimum supported Rust version to 1.40 +- Update `actix-web` & `actix-http` dependencies to beta.1 +- Bump minimum supported Rust version to 1.40 ## [3.0.0-alpha.1] - 2020-05-08 -* Update the actix-web dependency to 3.0.0-alpha.1 -* Update the actix dependency to 0.10.0-alpha.2 -* Update the actix-http dependency to 2.0.0-alpha.3 +- Update the actix-web dependency to 3.0.0-alpha.1 +- Update the actix dependency to 0.10.0-alpha.2 +- Update the actix-http dependency to 2.0.0-alpha.3 ## [2.0.0] - 2019-12-20 -* Release +- Release ## [2.0.0-alpha.1] - 2019-12-15 -* Migrate to actix-web 2.0.0 +- Migrate to actix-web 2.0.0 ## [1.0.4] - 2019-12-07 -* Allow comma-separated websocket subprotocols without spaces (#1172) +- Allow comma-separated websocket subprotocols without spaces (#1172) ## [1.0.3] - 2019-11-14 -* Update actix-web and actix-http dependencies +- Update actix-web and actix-http dependencies ## [1.0.2] - 2019-07-20 -* Add `ws::start_with_addr()`, returning the address of the created actor, along +- Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`. -* Add support for specifying protocols on websocket handshake #835 +- Add support for specifying protocols on websocket handshake #835 ## [1.0.1] - 2019-06-28 -* Allow to use custom ws codec with `WebsocketContext` #925 +- Allow to use custom ws codec with `WebsocketContext` #925 ## [1.0.0] - 2019-05-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.3] - 2019-04-02 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.2] - 2019-03-29 -* Update actix-http and actix-web +- Update actix-http and actix-web ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 309274563..0d881d303 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -4,101 +4,101 @@ ## 0.5.0-beta.6 - 2021-12-11 -* No significant changes since `0.5.0-beta.5`. +- No significant changes since `0.5.0-beta.5`. ## 0.5.0-beta.5 - 2021-10-20 -* Improve error recovery potential when macro input is invalid. [#2410] -* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] -* Minimum supported Rust version (MSRV) is now 1.52. +- Improve error recovery potential when macro input is invalid. [#2410] +- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] +- Minimum supported Rust version (MSRV) is now 1.52. [#2410]: https://github.com/actix/actix-web/pull/2410 [#2409]: https://github.com/actix/actix-web/pull/2409 ## 0.5.0-beta.4 - 2021-09-09 -* In routing macros, paths are now validated at compile time. [#2350] -* Minimum supported Rust version (MSRV) is now 1.51. +- In routing macros, paths are now validated at compile time. [#2350] +- Minimum supported Rust version (MSRV) is now 1.51. [#2350]: https://github.com/actix/actix-web/pull/2350 ## 0.5.0-beta.3 - 2021-06-17 -* No notable changes. +- No notable changes. ## 0.5.0-beta.2 - 2021-03-09 -* Preserve doc comments when using route macros. [#2022] -* Add `name` attribute to `route` macro. [#1934] +- Preserve doc comments when using route macros. [#2022] +- Add `name` attribute to `route` macro. [#1934] [#2022]: https://github.com/actix/actix-web/pull/2022 [#1934]: https://github.com/actix/actix-web/pull/1934 ## 0.5.0-beta.1 - 2021-02-10 -* Use new call signature for `System::new`. +- Use new call signature for `System::new`. ## 0.4.0 - 2020-09-20 -* Added compile success and failure testing. [#1677] -* Add `route` macro for supporting multiple HTTP methods guards. [#1674] +- Added compile success and failure testing. [#1677] +- Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 ## 0.3.0 - 2020-09-11 -* No significant changes from `0.3.0-beta.1`. +- No significant changes from `0.3.0-beta.1`. ## 0.3.0-beta.1 - 2020-07-14 -* Add main entry-point macro that uses re-exported runtime. [#1559] +- Add main entry-point macro that uses re-exported runtime. [#1559] [#1559]: https://github.com/actix/actix-web/pull/1559 ## 0.2.2 - 2020-05-23 -* Add resource middleware on actix-web-codegen [#1467] +- Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 ## 0.2.1 - 2020-02-25 -* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] -* Allow the handler function to be named as `config` [#1290] +- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] +- Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 ## 0.2.0 - 2019-12-13 -* Generate code for actix-web 2.0 +- Generate code for actix-web 2.0 ## 0.1.3 - 2019-10-14 -* Bump up `syn` & `quote` to 1.0 -* Provide better error message +- Bump up `syn` & `quote` to 1.0 +- Provide better error message ## 0.1.2 - 2019-06-04 -* Add macros for head, options, trace, connect and patch http methods +- Add macros for head, options, trace, connect and patch http methods ## 0.1.1 - 2019-06-01 -* Add syn "extra-traits" feature +- Add syn "extra-traits" feature ## 0.1.0 - 2019-05-18 -* Release +- Release ## 0.1.0-beta.1 - 2019-04-20 -* Gen code for actix-web 1.0.0-beta.1 +- Gen code for actix-web 1.0.0-beta.1 ## 0.1.0-alpha.6 - 2019-04-14 -* Gen code for actix-web 1.0.0-alpha.6 +- Gen code for actix-web 1.0.0-alpha.6 ## 0.1.0-alpha.1 - 2019-03-28 -* Initial impl +- Initial impl diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7b822930c..b5144b7a2 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,75 +1,75 @@ # Changes ## Unreleased - 2021-xx-xx -* Rename `Connector::{ssl => openssl}`. [#2503] -* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Rename `Connector::{ssl => openssl}`. [#2503] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] [#2503]: https://github.com/actix/actix-web/pull/2503 ## 3.0.0-beta.14 - 2021-12-17 -* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] +- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510] [#2510]: https://github.com/actix/actix-web/pull/2510 ## 3.0.0-beta.13 - 2021-12-11 -* No significant changes since `3.0.0-beta.12`. +- No significant changes since `3.0.0-beta.12`. ## 3.0.0-beta.12 - 2021-11-30 -* Update `actix-tls` to `3.0.0-rc.1`. [#2474] +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] [#2474]: https://github.com/actix/actix-web/pull/2474 ## 3.0.0-beta.11 - 2021-11-22 -* No significant changes from `3.0.0-beta.10`. +- No significant changes from `3.0.0-beta.10`. ## 3.0.0-beta.10 - 2021-11-15 -* No significant changes from `3.0.0-beta.9`. +- No significant changes from `3.0.0-beta.9`. ## 3.0.0-beta.9 - 2021-10-20 -* Updated rustls to v0.20. [#2414] +- Updated rustls to v0.20. [#2414] [#2414]: https://github.com/actix/actix-web/pull/2414 ## 3.0.0-beta.8 - 2021-09-09 ### Changed -* Send headers within the redirect requests. [#2310] +- Send headers within the redirect requests. [#2310] [#2310]: https://github.com/actix/actix-web/pull/2310 ## 3.0.0-beta.7 - 2021-06-26 ### Changed -* Change compression algorithm features flags. [#2250] +- Change compression algorithm features flags. [#2250] [#2250]: https://github.com/actix/actix-web/pull/2250 ## 3.0.0-beta.6 - 2021-06-17 -* No significant changes since 3.0.0-beta.5. +- No significant changes since 3.0.0-beta.5. ## 3.0.0-beta.5 - 2021-04-17 ### Removed -* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] [#2148]: https://github.com/actix/actix-web/pull/2148 ## 3.0.0-beta.4 - 2021-04-02 ### Added -* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] +- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] ### Changed -* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] -* Fix http/https encoding when enabling `compress` feature. [#2116] -* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094] [#2081]: https://github.com/actix/actix-web/pull/2081 @@ -80,16 +80,16 @@ ## 3.0.0-beta.3 - 2021-03-08 ### Added -* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] -* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] +- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed -* Feature `cookies` is now optional and enabled by default. [#1981] -* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] -* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] +- Feature `cookies` is now optional and enabled by default. [#1981] +- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] +- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] ### Removed -* `ClientBuilder::default` function [#2008] +- `ClientBuilder::default` function [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 @@ -100,18 +100,18 @@ ## 3.0.0-beta.2 - 2021-02-10 ### Added -* `ClientRequest::insert_header` method which allows using typed headers. [#1869] -* `ClientRequest::append_header` method which allows using typed headers. [#1869] -* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] +- `ClientRequest::insert_header` method which allows using typed headers. [#1869] +- `ClientRequest::append_header` method which allows using typed headers. [#1869] +- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed -* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] +- 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] +- `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 @@ -120,32 +120,32 @@ ## 3.0.0-beta.1 - 2021-01-07 ### Changed -* Update `rand` to `0.8` -* Update `bytes` to `1.0`. [#1813] -* Update `rust-tls` to `0.19`. [#1813] +- Update `rand` to `0.8` +- Update `bytes` to `1.0`. [#1813] +- Update `rust-tls` to `0.19`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 ### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. +- Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. [#1773] +- Upgrade `serde_urlencoded` to `0.7`. [#1773] [#1773]: https://github.com/actix/actix-web/pull/1773 ## 2.0.1 - 2020-10-30 ### Changed -* Upgrade `base64` to `0.13`. [#1744] -* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] +- Upgrade `base64` to `0.13`. [#1744] +- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature +- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 @@ -155,209 +155,209 @@ ## 2.0.0 - 2020-09-11 ### Changed -* `Client::build` was renamed to `Client::builder`. +- `Client::build` was renamed to `Client::builder`. ## 2.0.0-beta.4 - 2020-09-09 ### Changed -* Update actix-codec & actix-tls dependencies. +- Update actix-codec & actix-tls dependencies. ## 2.0.0-beta.3 - 2020-08-17 ### Changed -* Update `rustls` to 0.18 +- Update `rustls` to 0.18 ## 2.0.0-beta.2 - 2020-07-21 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.2 +- Update `actix-http` dependency to 2.0.0-beta.2 ## [2.0.0-beta.1] - 2020-07-14 ### Changed -* Update `actix-http` dependency to 2.0.0-beta.1 +- Update `actix-http` dependency to 2.0.0-beta.1 ## [2.0.0-alpha.2] - 2020-05-21 ### Changed -* Implement `std::error::Error` for our custom errors [#1422] -* Bump minimum supported Rust version to 1.40 -* Update `base64` dependency to 0.12 +- Implement `std::error::Error` for our custom errors [#1422] +- Bump minimum supported Rust version to 1.40 +- Update `base64` dependency to 0.12 [#1422]: https://github.com/actix/actix-web/pull/1422 ## [2.0.0-alpha.1] - 2020-03-11 -* Update `actix-http` dependency to 2.0.0-alpha.2 -* Update `rustls` dependency to 0.17 -* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration -* ClientBuilder allowing to set max_http_version to limit HTTP version to be used +- Update `actix-http` dependency to 2.0.0-alpha.2 +- Update `rustls` dependency to 0.17 +- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration +- ClientBuilder allowing to set max_http_version to limit HTTP version to be used ## [1.0.1] - 2019-12-15 -* Fix compilation with default features off +- Fix compilation with default features off ## [1.0.0] - 2019-12-13 -* Release +- Release ## [1.0.0-alpha.3] -* Migrate to `std::future` +- Migrate to `std::future` ## [0.2.8] - 2019-11-06 -* Add support for setting query from Serialize type for client request. +- Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 ### Added -* Remaining getter methods for `ClientRequest`'s private `head` field #1101 +- Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 ### Added -* Export frozen request related types. +- Export frozen request related types. ## [0.2.5] - 2019-09-11 ### Added -* Add `FrozenClientRequest` to support retries for sending HTTP requests +- Add `FrozenClientRequest` to support retries for sending HTTP requests ### Changed -* Ensure that the `Host` header is set when initiating a WebSocket client connection. +- Ensure that the `Host` header is set when initiating a WebSocket client connection. ## [0.2.4] - 2019-08-13 ### Changed -* Update percent-encoding to "2.1" +- Update percent-encoding to "2.1" -* Update serde_urlencoded to "0.6.1" +- Update serde_urlencoded to "0.6.1" ## [0.2.3] - 2019-08-01 ### Added -* Add `rustls` support +- Add `rustls` support ## [0.2.2] - 2019-07-01 ### Changed -* Always append a colon after username in basic auth +- Always append a colon after username in basic auth -* Upgrade `rand` dependency version to 0.7 +- Upgrade `rand` dependency version to 0.7 ## [0.2.1] - 2019-06-05 ### Added -* Add license files +- Add license files ## [0.2.0] - 2019-05-12 ### Added -* Allow to send headers in `Camel-Case` form. +- Allow to send headers in `Camel-Case` form. ### Changed -* Upgrade actix-http dependency. +- Upgrade actix-http dependency. ## [0.1.1] - 2019-04-19 ### Added -* Allow to specify server address for http and ws requests. +- Allow to specify server address for http and ws requests. ### Changed -* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref +- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref ## [0.1.0] - 2019-04-16 -* No changes +- No changes ## [0.1.0-alpha.6] - 2019-04-14 ### Changed -* Do not set default headers for websocket request +- Do not set default headers for websocket request ## [0.1.0-alpha.5] - 2019-04-12 ### Changed -* Do not set any default headers +- Do not set any default headers ### Added -* Add Debug impl for BoxedSocket +- Add Debug impl for BoxedSocket ## [0.1.0-alpha.4] - 2019-04-08 ### Changed -* Update actix-http dependency +- Update actix-http dependency ## [0.1.0-alpha.3] - 2019-04-02 ### Added -* Export `MessageBody` type +- Export `MessageBody` type -* `ClientResponse::json()` - Loads and parse `application/json` encoded body +- `ClientResponse::json()` - Loads and parse `application/json` encoded body ### Changed -* `ClientRequest::json()` accepts reference instead of object. +- `ClientRequest::json()` accepts reference instead of object. -* `ClientResponse::body()` does not consume response object. +- `ClientResponse::body()` does not consume response object. -* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` +- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` ## [0.1.0-alpha.2] - 2019-03-29 ### Added -* Per request and session wide request timeout. +- Per request and session wide request timeout. -* Session wide headers. +- Session wide headers. -* Session wide basic and bearer auth. +- Session wide basic and bearer auth. -* Re-export `actix_http::client::Connector`. +- Re-export `actix_http::client::Connector`. ### Changed -* Allow to override request's uri +- Allow to override request's uri -* Export `ws` sub-module with websockets related types +- Export `ws` sub-module with websockets related types ## [0.1.0-alpha.1] - 2019-03-28 -* Initial impl +- Initial impl From b3ac918d7001a351ecec84317b053e862b6c561c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:34:48 +0000 Subject: [PATCH 209/861] update itoa to v1 --- Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/header/shared/quality.rs | 18 ++++++++++++------ awc/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02bef3af6..d15f26172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -itoa = "0.4" +itoa = "1" language-tags = "0.3" once_cell = "1.5" log = "0.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9f93bf6d2..e4eadd37c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,7 +60,7 @@ h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" -itoa = "0.4" +itoa = "1" language-tags = "0.3" local-channel = "0.1" log = "0.4" diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs index 5321c754d..c2f08edc2 100644 --- a/actix-http/src/header/shared/quality.rs +++ b/actix-http/src/header/shared/quality.rs @@ -87,7 +87,7 @@ impl fmt::Display for Quality { // 0 is already handled so it's not possible to have a trailing 0 in this range // we can just write the integer - itoa::fmt(f, x) + itoa_fmt(f, x) } else if x < 100 { // x in is range 10–99 @@ -95,21 +95,21 @@ impl fmt::Display for Quality { if x % 10 == 0 { // trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } else { // x is in range 100–999 if x % 100 == 0 { // two trailing 0s, divide by 100 and write - itoa::fmt(f, x / 100) + itoa_fmt(f, x / 100) } else if x % 10 == 0 { // one trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } } @@ -117,6 +117,12 @@ impl fmt::Display for Quality { } } +/// Write integer to a `fmt::Write`. +pub fn itoa_fmt(mut wr: W, value: V) -> fmt::Result { + let mut buf = itoa::Buffer::new(); + wr.write_str(buf.format(value)) +} + #[derive(Debug, Clone, Display, Error)] #[display(fmt = "quality out of bounds")] #[non_exhaustive] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f9a541c7e..cf12f2383 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -74,7 +74,7 @@ futures-core = { version = "0.3.7", default-features = false, features = ["alloc futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" http = "0.2.5" -itoa = "0.4" +itoa = "1" log =" 0.4" mime = "0.3" percent-encoding = "2.1" From 324eba7e0b5a0451025a61828da89dbb20f17966 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:41:44 +0000 Subject: [PATCH 210/861] tighten tokio version range to prevent RUSTSEC-2021-0124 --- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0c205fc2a..e1c875a1f 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -48,7 +48,7 @@ serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -tokio = { version = "1.2", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e4eadd37c..3ad3d786e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -tokio = { version = "1.2", features = ["net", "rt", "macros"] } +tokio = { version = "1.8", features = ["net", "rt", "macros"] } [[example]] name = "ws" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index c7145e542..595c14d7e 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -30,5 +30,5 @@ twoway = "0.2" actix-rt = "2.2" actix-http = "3.0.0-beta.16" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 7957b3a9c..4a4615820 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -45,4 +45,4 @@ serde_json = "1" serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true } -tokio = { version = "1.2", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3f213f378..d57f139f6 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -23,7 +23,7 @@ bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cf12f2383..4b29aac16 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -83,7 +83,7 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -tokio = { version = "1", features = ["sync"] } +tokio = { version = "1.8", features = ["sync"] } cookie = { version = "0.15", features = ["percent-encode"], optional = true } From 1769812d0b19cd9df59c3c60b4c35f495bee5d1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 08:43:38 +0000 Subject: [PATCH 211/861] bump outdated deps --- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3ad3d786e..2958a1c77 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,7 +68,7 @@ mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" rand = "0.8" -sha-1 = "0.9" +sha-1 = "0.10" smallvec = "1.6.1" # tls diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index afd39dfd3..c63448bc7 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,7 +21,7 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -firestorm = "0.4" +firestorm = "0.5" http = { version = "0.2.3", optional = true } log = "0.4" regex = "1.5" @@ -29,7 +29,7 @@ serde = "1" [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -firestorm = { version = "0.4", features = ["enable_system_time"] } +firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } From cd025f5c0ba7774263cbae38fd6b4c652b4d54a3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 22 Dec 2021 15:00:32 +0000 Subject: [PATCH 212/861] allow any body type in Resource (#2526) --- CHANGES.md | 4 +++ actix-http/Cargo.toml | 1 - src/middleware/compat.rs | 9 +++++ src/middleware/mod.rs | 4 +++ src/middleware/noop.rs | 37 +++++++++++++++++++++ src/resource.rs | 72 +++++++++++++++++++++++++++++----------- src/scope.rs | 10 +++--- 7 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 src/middleware/noop.rs diff --git a/CHANGES.md b/CHANGES.md index 8e030819f..07c247554 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- No longer require `Resource` service body type to be boxed. [#2526] + +[#2526]: https://github.com/actix/actix-web/pull/2526 ## 4.0.0-beta.15 - 2021-12-17 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2958a1c77..c15f5ee28 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,6 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] } h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index d49c461c4..3386240b7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -38,6 +38,15 @@ pub struct Compat { transform: T, } +#[cfg(test)] +impl Compat { + pub(crate) fn noop() -> Self { + Self { + transform: super::Noop, + } + } +} + impl Compat { /// Wrap a middleware to give it broader compatibility. pub fn new(middleware: T) -> Self { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 0da9b9b2e..a781052a6 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,6 +5,8 @@ mod condition; mod default_headers; mod err_handlers; mod logger; +#[cfg(test)] +mod noop; mod normalize; pub use self::compat::Compat; @@ -12,6 +14,8 @@ pub use self::condition::Condition; pub use self::default_headers::DefaultHeaders; pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::logger::Logger; +#[cfg(test)] +pub(crate) use self::noop::Noop; pub use self::normalize::{NormalizePath, TrailingSlash}; #[cfg(feature = "__compress")] diff --git a/src/middleware/noop.rs b/src/middleware/noop.rs new file mode 100644 index 000000000..ae7da1d81 --- /dev/null +++ b/src/middleware/noop.rs @@ -0,0 +1,37 @@ +//! A no-op middleware. See [Noop] for docs. + +use actix_utils::future::{ready, Ready}; + +use crate::dev::{Service, Transform}; + +/// A no-op middleware that passes through request and response untouched. +pub(crate) struct Noop; + +impl, Req> Transform for Noop { + type Response = S::Response; + type Error = S::Error; + type Transform = NoopService; + type InitError = (); + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(NoopService { service })) + } +} + +#[doc(hidden)] +pub(crate) struct NoopService { + service: S, +} + +impl, Req> Service for NoopService { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + crate::dev::forward_ready!(service); + + fn call(&self, req: Req) -> Self::Future { + self.service.call(req) + } +} diff --git a/src/resource.rs b/src/resource.rs index c13544063..d94d2a464 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; -use actix_http::Extensions; +use actix_http::{body::BoxBody, Extensions}; use actix_router::{IntoPatterns, Patterns}; use actix_service::{ apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory, @@ -45,7 +45,7 @@ use crate::{ /// /// If no matching route could be found, *405* response code get returned. /// Default behavior could be overridden with `default_resource()` method. -pub struct Resource { +pub struct Resource { endpoint: T, rdef: Patterns, name: Option, @@ -54,6 +54,7 @@ pub struct Resource { guards: Vec>, default: BoxedHttpServiceFactory, factory_ref: Rc>>, + _phantom: PhantomData, } impl Resource { @@ -71,19 +72,21 @@ impl Resource { default: boxed::factory(fn_service(|req: ServiceRequest| async { Ok(req.into_response(HttpResponse::MethodNotAllowed())) })), + _phantom: PhantomData, } } } -impl Resource +impl Resource where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B: MessageBody, { /// Set resource name. /// @@ -252,26 +255,28 @@ where /// type (i.e modify response's body). /// /// **Note**: middlewares get called in opposite order of middlewares registration. - pub fn wrap( + pub fn wrap( self, mw: M, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where M: Transform< T::Service, ServiceRequest, - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1: MessageBody, { Resource { endpoint: apply(mw, self.endpoint), @@ -282,6 +287,7 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, + _phantom: PhantomData, } } @@ -319,21 +325,23 @@ where /// .route(web::get().to(index))); /// } /// ``` - pub fn wrap_fn( + pub fn wrap_fn( self, mw: F, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, + B1, > where F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future>, + R: Future, Error>>, + B1: MessageBody, { Resource { endpoint: apply_fn_factory(self.endpoint, mw), @@ -344,6 +352,7 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, + _phantom: PhantomData, } } @@ -371,15 +380,16 @@ where } } -impl HttpServiceFactory for Resource +impl HttpServiceFactory for Resource where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), > + 'static, + B: MessageBody + 'static, { fn register(mut self, config: &mut AppService) { let guards = if self.guards.is_empty() { @@ -411,7 +421,9 @@ where req.add_data_container(Rc::clone(data)); } - srv.call(req) + let fut = srv.call(req); + + async { Ok(fut.await?.map_into_boxed_body()) } }); config.register_service(rdef, guards, endpoint, None) @@ -534,11 +546,11 @@ mod tests { >, > { web::resource("/test-compat") - // .wrap_fn(|req, srv| { - // let fut = srv.call(req); - // async { Ok(fut.await?.map_into_right_body::<()>()) } - // }) - .wrap(Compat::new(DefaultHeaders::new())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .wrap(Compat::noop()) .route(web::get().to(|| async { "hello" })) } @@ -801,4 +813,26 @@ mod tests { let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[actix_rt::test] + async fn test_middleware_body_type() { + let srv = init_service( + App::new().service( + web::resource("/test") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .route(web::get().to(|| async { "hello" })), + ), + ) + .await; + + // test if `try_into_bytes()` is preserved across scope layer + use actix_http::body::MessageBody as _; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&srv, req).await; + let body = resp.into_body(); + assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref()); + } } diff --git a/src/scope.rs b/src/scope.rs index 35bbb50ba..7f9a94875 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -616,11 +616,11 @@ mod tests { >, > { web::scope("/test-compat") - // .wrap_fn(|req, srv| { - // let fut = srv.call(req); - // async { Ok(fut.await?.map_into_right_body::<()>()) } - // }) - .wrap(Compat::new(DefaultHeaders::new())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .wrap(Compat::noop()) .service(web::resource("").route(web::get().to(|| async { "hello" }))) } From 7b1512d863ed63cb7a1fab51ff358b24b33c5f19 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 22 Dec 2021 18:48:59 +0300 Subject: [PATCH 213/861] allow any body type in Scope (#2523) --- CHANGES.md | 2 ++ src/middleware/compat.rs | 2 +- src/scope.rs | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 07c247554..a43b3ee41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx ### Changed +- No longer require `Scope` service body type to be boxed. [#2523] - No longer require `Resource` service body type to be boxed. [#2526] +[#2523]: https://github.com/actix/actix-web/pull/2523 [#2526]: https://github.com/actix/actix-web/pull/2526 diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 3386240b7..18c9ff6a7 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -17,7 +17,7 @@ use crate::{ }; /// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), -/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). +/// and [`Condition`](super::Condition). /// /// # Examples /// ``` diff --git a/src/scope.rs b/src/scope.rs index 7f9a94875..1fd282f61 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,9 @@ use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc}; -use actix_http::{body::BoxBody, Extensions}; +use actix_http::{ + body::{BoxBody, MessageBody}, + Extensions, +}; use actix_router::{ResourceDef, Router}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, @@ -399,15 +402,16 @@ where } } -impl HttpServiceFactory for Scope +impl HttpServiceFactory for Scope where T: ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), > + 'static, + B: MessageBody + 'static, { fn register(mut self, config: &mut AppService) { // update default resource if needed @@ -457,7 +461,9 @@ where req.add_data_container(Rc::clone(data)); } - srv.call(req) + let fut = srv.call(req); + + async { Ok(fut.await?.map_into_boxed_body()) } }); // register final service @@ -980,6 +986,29 @@ mod tests { ); } + #[actix_rt::test] + async fn test_middleware_body_type() { + // Compile test that Scope accepts any body type; test for `EitherBody` + let srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { Ok(fut.await?.map_into_right_body::<()>()) } + }) + .service(web::resource("/test").route(web::get().to(|| async { "hello" }))), + ), + ) + .await; + + // test if `MessageBody::try_into_bytes()` is preserved across scope layer + use actix_http::body::MessageBody as _; + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&srv, req).await; + let body = resp.into_body(); + assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref()); + } + #[actix_rt::test] async fn test_middleware_fn() { let srv = init_service( From 1296e07c4830f0ab2e2864a6fc9faa93972e5935 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 24 Dec 2021 17:47:47 +0000 Subject: [PATCH 214/861] relax unpin bounds on payload types (#2545) --- actix-http/CHANGES.md | 9 ++++ actix-http/src/encoding/decoder.rs | 39 ++++++++------- actix-http/src/h1/dispatcher.rs | 7 +-- actix-http/src/h1/payload.rs | 76 +++++++++++++++++------------- actix-http/src/h1/utils.rs | 4 +- actix-http/src/h2/dispatcher.rs | 4 +- actix-http/src/h2/mod.rs | 11 +++++ actix-http/src/lib.rs | 3 +- actix-http/src/payload.rs | 73 +++++++++++++++++----------- actix-http/src/requests/request.rs | 11 +++-- actix-http/src/test.rs | 2 +- actix-http/tests/test_rustls.rs | 33 +++++++++---- actix-multipart/src/server.rs | 2 +- awc/src/client/connection.rs | 4 +- awc/src/client/h1proto.rs | 16 +++++-- awc/src/response.rs | 6 +-- awc/src/sender.rs | 15 +++--- awc/src/test.rs | 5 +- src/dev.rs | 2 +- src/response/builder.rs | 36 ++++++-------- src/service.rs | 4 +- src/test/test_request.rs | 21 +++++---- 22 files changed, 229 insertions(+), 154 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3b45e934f..adc4c35c7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,8 +3,17 @@ ## Unreleased - 2021-xx-xx ### Changes - `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] +- `Payload` inner fields are now named. [#2545] +- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545] +- `impl Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545] +- `impl Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545] +- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545] + +### Removed +- `h1::Payload::readany`. [#2545] [#2527]: https://github.com/actix/actix-web/pull/2527 +[#2545]: https://github.com/actix/actix-web/pull/2545 ## 3.0.0-beta.16 - 2021-12-17 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index a46e330c9..0f519637a 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -28,11 +28,14 @@ use crate::{ const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; -pub struct Decoder { - decoder: Option, - stream: S, - eof: bool, - fut: Option, ContentDecoder), io::Error>>>, +pin_project_lite::pin_project! { + pub struct Decoder { + decoder: Option, + #[pin] + stream: S, + eof: bool, + fut: Option, ContentDecoder), io::Error>>>, + } } impl Decoder @@ -89,42 +92,44 @@ where impl Stream for Decoder where - S: Stream> + Unpin, + S: Stream>, { type Item = Result; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + loop { - if let Some(ref mut fut) = self.fut { + if let Some(ref mut fut) = this.fut { let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; - self.decoder = Some(decoder); - self.fut.take(); + *this.decoder = Some(decoder); + this.fut.take(); if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } } - if self.eof { + if *this.eof { return Poll::Ready(None); } - match ready!(Pin::new(&mut self.stream).poll_next(cx)) { + match ready!(this.stream.as_mut().poll_next(cx)) { Some(Err(err)) => return Poll::Ready(Some(Err(err))), Some(Ok(chunk)) => { - if let Some(mut decoder) = self.decoder.take() { + if let Some(mut decoder) = this.decoder.take() { if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE { let chunk = decoder.feed_data(chunk)?; - self.decoder = Some(decoder); + *this.decoder = Some(decoder); if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } } else { - self.fut = Some(spawn_blocking(move || { + *this.fut = Some(spawn_blocking(move || { let chunk = decoder.feed_data(chunk)?; Ok((chunk, decoder)) })); @@ -137,9 +142,9 @@ where } None => { - self.eof = true; + *this.eof = true; - return if let Some(mut decoder) = self.decoder.take() { + return if let Some(mut decoder) = this.decoder.take() { match decoder.feed_eof() { Ok(Some(res)) => Poll::Ready(Some(Ok(res))), Ok(None) => Poll::Ready(None), diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 5c0cb64af..13055f08a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -646,10 +646,11 @@ where Payload is attached to Request and passed to Service::call where the state can be collected and consumed. */ - let (ps, pl) = Payload::create(false); - let (req1, _) = req.replace_payload(crate::Payload::H1(pl)); + let (sender, payload) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1 { payload }); req = req1; - *this.payload = Some(ps); + *this.payload = Some(sender); } // Request has no payload. diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index f912e0ba3..4d031c15a 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,9 +1,12 @@ //! Payload stream -use std::cell::RefCell; -use std::collections::VecDeque; -use std::pin::Pin; -use std::rc::{Rc, Weak}; -use std::task::{Context, Poll, Waker}; + +use std::{ + cell::RefCell, + collections::VecDeque, + pin::Pin, + rc::{Rc, Weak}, + task::{Context, Poll, Waker}, +}; use bytes::Bytes; use futures_core::Stream; @@ -22,39 +25,32 @@ pub enum PayloadStatus { /// Buffered stream of bytes chunks /// -/// Payload stores chunks in a vector. First chunk can be received with -/// `.readany()` method. Payload stream is not thread safe. Payload does not -/// notify current task when new data is available. +/// Payload stores chunks in a vector. First chunk can be received with `poll_next`. Payload does +/// not notify current task when new data is available. /// -/// Payload stream can be used as `Response` body stream. +/// Payload can be used as `Response` body stream. #[derive(Debug)] pub struct Payload { inner: Rc>, } impl Payload { - /// Create payload stream. + /// Creates a payload stream. /// - /// This method construct two objects responsible for bytes stream - /// generation. - /// - /// * `PayloadSender` - *Sender* side of the stream - /// - /// * `Payload` - *Receiver* side of the stream + /// This method construct two objects responsible for bytes stream generation: + /// - `PayloadSender` - *Sender* side of the stream + /// - `Payload` - *Receiver* side of the stream pub fn create(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); ( - PayloadSender { - inner: Rc::downgrade(&shared), - }, + PayloadSender::new(Rc::downgrade(&shared)), Payload { inner: shared }, ) } - /// Create empty payload - #[doc(hidden)] - pub fn empty() -> Payload { + /// Creates an empty payload. + pub(crate) fn empty() -> Payload { Payload { inner: Rc::new(RefCell::new(Inner::new(true))), } @@ -77,14 +73,6 @@ impl Payload { pub fn unread_data(&mut self, data: Bytes) { self.inner.borrow_mut().unread_data(data); } - - #[inline] - pub fn readany( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - self.inner.borrow_mut().readany(cx) - } } impl Stream for Payload { @@ -94,7 +82,7 @@ impl Stream for Payload { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - self.inner.borrow_mut().readany(cx) + Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx) } } @@ -104,6 +92,10 @@ pub struct PayloadSender { } impl PayloadSender { + fn new(inner: Weak>) -> Self { + Self { inner } + } + #[inline] pub fn set_error(&mut self, err: PayloadError) { if let Some(shared) = self.inner.upgrade() { @@ -227,7 +219,10 @@ impl Inner { self.len } - fn readany(&mut self, cx: &mut Context<'_>) -> Poll>> { + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < MAX_BUFFER_SIZE; @@ -257,8 +252,18 @@ impl Inner { #[cfg(test)] mod tests { - use super::*; + use std::panic::{RefUnwindSafe, UnwindSafe}; + use actix_utils::future::poll_fn; + use static_assertions::{assert_impl_all, assert_not_impl_any}; + + use super::*; + + assert_impl_all!(Payload: Unpin); + assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + + assert_impl_all!(Inner: Unpin, Send, Sync); + assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { @@ -270,7 +275,10 @@ mod tests { assert_eq!( Bytes::from("data"), - poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() + poll_fn(|cx| Pin::new(&mut payload).poll_next(cx)) + .await + .unwrap() + .unwrap() ); } } diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 131c7f1ed..5c11b1dab 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -45,7 +45,7 @@ where impl Future for SendResponse where T: AsyncRead + AsyncWrite + Unpin, - B: MessageBody + Unpin, + B: MessageBody, B::Error: Into, { type Output = Result, Error>; @@ -81,7 +81,7 @@ where // body is done when item is None body_done = item.is_none(); if body_done { - let _ = this.body.take(); + this.body.set(None); } let framed = this.framed.as_mut().as_pin_mut().unwrap(); framed diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 8fbefe6de..7f0f15ee6 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -108,8 +108,8 @@ where match Pin::new(&mut this.connection).poll_accept(cx)? { Poll::Ready(Some((req, tx))) => { let (parts, body) = req.into_parts(); - let pl = crate::h2::Payload::new(body); - let pl = Payload::H2(pl); + let payload = crate::h2::Payload::new(body); + let pl = Payload::H2 { payload }; let mut req = Request::with_payload(pl); let head = req.head_mut(); diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index cbcb6d0fc..47d51b420 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -98,3 +98,14 @@ where } } } + +#[cfg(test)] +mod tests { + use std::panic::{RefUnwindSafe, UnwindSafe}; + + use static_assertions::assert_impl_all; + + use super::*; + + assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe); +} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 2b7bc730b..f2b415790 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -58,7 +58,8 @@ pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; pub use self::message::ConnectionType; pub use self::message::Message; -pub use self::payload::{Payload, PayloadStream}; +#[allow(deprecated)] +pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 69840e7c1..c9f338c7d 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,70 +1,89 @@ use std::{ + mem, pin::Pin, task::{Context, Poll}, }; use bytes::Bytes; use futures_core::Stream; -use h2::RecvStream; use crate::error::PayloadError; -// TODO: rename to boxed payload -/// A boxed payload. -pub type PayloadStream = Pin>>>; +/// A boxed payload stream. +pub type BoxedPayloadStream = Pin>>>; -/// A streaming payload. -pub enum Payload { - None, - H1(crate::h1::Payload), - H2(crate::h2::Payload), - Stream(S), +#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] +pub type PayloadStream = BoxedPayloadStream; + +pin_project_lite::pin_project! { + /// A streaming payload. + #[project = PayloadProj] + pub enum Payload { + None, + H1 { payload: crate::h1::Payload }, + H2 { payload: crate::h2::Payload }, + Stream { #[pin] payload: S }, + } } impl From for Payload { - fn from(v: crate::h1::Payload) -> Self { - Payload::H1(v) + fn from(payload: crate::h1::Payload) -> Self { + Payload::H1 { payload } } } impl From for Payload { - fn from(v: crate::h2::Payload) -> Self { - Payload::H2(v) + fn from(payload: crate::h2::Payload) -> Self { + Payload::H2 { payload } } } -impl From for Payload { - fn from(v: RecvStream) -> Self { - Payload::H2(crate::h2::Payload::new(v)) +impl From for Payload { + fn from(stream: h2::RecvStream) -> Self { + Payload::H2 { + payload: crate::h2::Payload::new(stream), + } } } -impl From for Payload { - fn from(pl: PayloadStream) -> Self { - Payload::Stream(pl) +impl From for Payload { + fn from(payload: BoxedPayloadStream) -> Self { + Payload::Stream { payload } } } impl Payload { /// Takes current payload and replaces it with `None` value pub fn take(&mut self) -> Payload { - std::mem::replace(self, Payload::None) + mem::replace(self, Payload::None) } } impl Stream for Payload where - S: Stream> + Unpin, + S: Stream>, { type Item = Result; #[inline] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - Payload::None => Poll::Ready(None), - Payload::H1(ref mut pl) => pl.readany(cx), - Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx), - Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx), + match self.project() { + PayloadProj::None => Poll::Ready(None), + PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::Stream { payload } => payload.poll_next(cx), } } } + +#[cfg(test)] +mod tests { + use std::panic::{RefUnwindSafe, UnwindSafe}; + + use static_assertions::{assert_impl_all, assert_not_impl_any}; + + use super::*; + + assert_impl_all!(Payload: Unpin); + assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); +} diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 0254a8f11..4eaaba8e1 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -10,11 +10,12 @@ use std::{ use http::{header, Method, Uri, Version}; use crate::{ - header::HeaderMap, Extensions, HttpMessage, Message, Payload, PayloadStream, RequestHead, + header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, + RequestHead, }; /// An HTTP request. -pub struct Request

{ +pub struct Request

{ pub(crate) payload: Payload

, pub(crate) head: Message, pub(crate) conn_data: Option>, @@ -46,7 +47,7 @@ impl

HttpMessage for Request

{ } } -impl From> for Request { +impl From> for Request { fn from(head: Message) -> Self { Request { head, @@ -57,10 +58,10 @@ impl From> for Request { } } -impl Request { +impl Request { /// Create new Request instance #[allow(clippy::new_without_default)] - pub fn new() -> Request { + pub fn new() -> Request { Request { head: Message::new(), payload: Payload::None, diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ea80345fe..1f76498ef 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -120,7 +120,7 @@ impl TestRequest { } /// Set request payload. - pub fn set_payload>(&mut self, data: B) -> &mut Self { + pub fn set_payload(&mut self, data: impl Into) -> &mut Self { let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); parts(&mut self.0).payload = Some(payload.into()); diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 42ff0dba1..51fefae72 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -7,6 +7,7 @@ use std::{ io::{self, BufReader, Write}, net::{SocketAddr, TcpStream as StdTcpStream}, sync::Arc, + task::Poll, }; use actix_http::{ @@ -16,25 +17,37 @@ use actix_http::{ Error, HttpService, Method, Request, Response, StatusCode, Version, }; use actix_http_test::test_server; +use actix_rt::pin; use actix_service::{fn_factory_with_config, fn_service}; use actix_tls::connect::rustls::webpki_roots_cert_store; -use actix_utils::future::{err, ok}; +use actix_utils::future::{err, ok, poll_fn}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; -use futures_core::Stream; -use futures_util::stream::{once, StreamExt as _}; +use futures_core::{ready, Stream}; +use futures_util::stream::once; use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName}; use rustls_pemfile::{certs, pkcs8_private_keys}; -async fn load_body(mut stream: S) -> Result +async fn load_body(stream: S) -> Result where - S: Stream> + Unpin, + S: Stream>, { - let mut body = BytesMut::new(); - while let Some(item) = stream.next().await { - body.extend_from_slice(&item?) - } - Ok(body) + let mut buf = BytesMut::new(); + + pin!(stream); + + poll_fn(|cx| loop { + let body = stream.as_mut(); + + match ready!(body.poll_next(cx)) { + Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), + None => return Poll::Ready(Ok(())), + Some(Err(err)) => return Poll::Ready(Err(err)), + } + }) + .await?; + + Ok(buf) } fn tls_config() -> RustlsServerConfig { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 8eabcee10..239f7f905 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1233,7 +1233,7 @@ mod tests { // and should not consume the payload match payload { - actix_web::dev::Payload::H1(_) => {} //expected + actix_web::dev::Payload::H1 { .. } => {} //expected _ => unreachable!(), } } diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs index 0e1f0bfec..456f119aa 100644 --- a/awc/src/client/connection.rs +++ b/awc/src/client/connection.rs @@ -267,7 +267,9 @@ where Connection::Tls(ConnectionType::H2(conn)) => { h2proto::send_request(conn, head.into(), body).await } - _ => unreachable!("Plain Tcp connection can be used only in Http1 protocol"), + _ => { + unreachable!("Plain TCP connection can be used only with HTTP/1.1 protocol") + } } }) } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 1028a2178..cf716db72 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -13,16 +13,17 @@ use actix_http::{ Payload, RequestHeadType, ResponseHead, StatusCode, }; use actix_utils::future::poll_fn; -use bytes::buf::BufMut; -use bytes::{Bytes, BytesMut}; +use bytes::{buf::BufMut, Bytes, BytesMut}; use futures_core::{ready, Stream}; use futures_util::SinkExt as _; use pin_project_lite::pin_project; use crate::BoxError; -use super::connection::{ConnectionIo, H1Connection}; -use super::error::{ConnectError, SendRequestError}; +use super::{ + connection::{ConnectionIo, H1Connection}, + error::{ConnectError, SendRequestError}, +}; pub(crate) async fn send_request( io: H1Connection, @@ -123,7 +124,12 @@ where Ok((head, Payload::None)) } - _ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))), + _ => Ok(( + head, + Payload::Stream { + payload: Box::pin(PlStream::new(framed)), + }, + )), } } diff --git a/awc/src/response.rs b/awc/src/response.rs index fefebd0a0..78cc339b4 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -10,8 +10,8 @@ use std::{ }; use actix_http::{ - error::PayloadError, header, header::HeaderMap, Extensions, HttpMessage, Payload, - PayloadStream, ResponseHead, StatusCode, Version, + error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions, + HttpMessage, Payload, ResponseHead, StatusCode, Version, }; use actix_rt::time::{sleep, Sleep}; use bytes::{Bytes, BytesMut}; @@ -23,7 +23,7 @@ use crate::cookie::{Cookie, ParseError as CookieParseError}; use crate::error::JsonPayloadError; /// Client Response -pub struct ClientResponse { +pub struct ClientResponse { pub(crate) head: ResponseHead, pub(crate) payload: Payload, pub(crate) timeout: ResponseTimeout, diff --git a/awc/src/sender.rs b/awc/src/sender.rs index f83a70a9b..29c814531 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -20,7 +20,7 @@ use futures_core::Stream; use serde::Serialize; #[cfg(feature = "__compress")] -use actix_http::{encoding::Decoder, header::ContentEncoding, Payload, PayloadStream}; +use actix_http::{encoding::Decoder, header::ContentEncoding, Payload}; use crate::{ any_body::AnyBody, @@ -91,7 +91,7 @@ impl SendClientRequest { #[cfg(feature = "__compress")] impl Future for SendClientRequest { - type Output = Result>>, SendRequestError>; + type Output = Result>, SendRequestError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); @@ -108,12 +108,13 @@ impl Future for SendClientRequest { res.into_client_response()._timeout(delay.take()).map_body( |head, payload| { if *response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) + Payload::Stream { + payload: Decoder::from_headers(payload, &head.headers), + } } else { - Payload::Stream(Decoder::new( - payload, - ContentEncoding::Identity, - )) + Payload::Stream { + payload: Decoder::new(payload, ContentEncoding::Identity), + } } }, ) diff --git a/awc/src/test.rs b/awc/src/test.rs index 1b41efc93..96ae1f0a1 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -65,7 +65,7 @@ impl TestResponse { /// Set response's payload pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = h1::Payload::empty(); + let (_, mut payload) = h1::Payload::create(true); payload.unread_data(data.into()); self.payload = Some(payload.into()); self @@ -90,7 +90,8 @@ impl TestResponse { if let Some(pl) = self.payload { ClientResponse::new(head, pl) } else { - ClientResponse::new(head, h1::Payload::empty().into()) + let (_, payload) = h1::Payload::create(true); + ClientResponse::new(head, payload.into()) } } } diff --git a/src/dev.rs b/src/dev.rs index 23a40f292..6e1970467 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -14,7 +14,7 @@ pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; -pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; +pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::{Server, ServerHandle}; pub use actix_service::{ diff --git a/src/response/builder.rs b/src/response/builder.rs index b500ab331..93d8ab567 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -429,9 +429,12 @@ mod tests { use actix_http::body; use super::*; - use crate::http::{ - header::{self, HeaderValue, CONTENT_TYPE}, - StatusCode, + use crate::{ + http::{ + header::{self, HeaderValue, CONTENT_TYPE}, + StatusCode, + }, + test::assert_body_eq, }; #[test] @@ -472,32 +475,23 @@ mod tests { #[actix_rt::test] async fn test_json() { - let resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + let res = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]); + let ct = res.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - body::to_bytes(resp.into_body()).await.unwrap().as_ref(), - br#"["v1","v2","v3"]"# - ); + assert_body_eq!(res, br#"["v1","v2","v3"]"#); - let resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + let res = HttpResponse::Ok().json(&["v1", "v2", "v3"]); + let ct = res.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - body::to_bytes(resp.into_body()).await.unwrap().as_ref(), - br#"["v1","v2","v3"]"# - ); + assert_body_eq!(res, br#"["v1","v2","v3"]"#); // content type override - let resp = HttpResponse::Ok() + let res = HttpResponse::Ok() .insert_header((CONTENT_TYPE, "text/json")) .json(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + let ct = res.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - body::to_bytes(resp.into_body()).await.unwrap().as_ref(), - br#"["v1","v2","v3"]"# - ); + assert_body_eq!(res, br#"["v1","v2","v3"]"#); } #[actix_rt::test] diff --git a/src/service.rs b/src/service.rs index 9ccf5274d..d5c381fa4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -7,7 +7,7 @@ use std::{ use actix_http::{ body::{BoxBody, EitherBody, MessageBody}, header::HeaderMap, - Extensions, HttpMessage, Method, Payload, PayloadStream, RequestHead, Response, + BoxedPayloadStream, Extensions, HttpMessage, Method, Payload, RequestHead, Response, ResponseHead, StatusCode, Uri, Version, }; use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url}; @@ -293,7 +293,7 @@ impl Resource for ServiceRequest { } impl HttpMessage for ServiceRequest { - type Stream = PayloadStream; + type Stream = BoxedPayloadStream; #[inline] /// Returns Request's headers. diff --git a/src/test/test_request.rs b/src/test/test_request.rs index fd3355ef3..5c4de9084 100644 --- a/src/test/test_request.rs +++ b/src/test/test_request.rs @@ -174,25 +174,28 @@ impl TestRequest { } /// Set request payload. - pub fn set_payload>(mut self, data: B) -> Self { + pub fn set_payload(mut self, data: impl Into) -> Self { self.req.set_payload(data); self } - /// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type` - /// header is set to `application/x-www-form-urlencoded`. - pub fn set_form(mut self, data: &T) -> Self { - let bytes = serde_urlencoded::to_string(data) + /// Serialize `data` to a URL encoded form and set it as the request payload. + /// + /// The `Content-Type` header is set to `application/x-www-form-urlencoded`. + pub fn set_form(mut self, data: impl Serialize) -> Self { + let bytes = serde_urlencoded::to_string(&data) .expect("Failed to serialize test data as a urlencoded form"); self.req.set_payload(bytes); self.req.insert_header(ContentType::form_url_encoded()); self } - /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is - /// set to `application/json`. - pub fn set_json(mut self, data: &T) -> Self { - let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); + /// Serialize `data` to JSON and set it as the request payload. + /// + /// The `Content-Type` header is set to `application/json`. + pub fn set_json(mut self, data: impl Serialize) -> Self { + let bytes = + serde_json::to_string(&data).expect("Failed to serialize test data to json"); self.req.set_payload(bytes); self.req.insert_header(ContentType::json()); self From d2590fd46cbab9cf96b3e6864430f675f4512835 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 02:33:37 +0000 Subject: [PATCH 215/861] `ClientRequest::send_body` takes `impl MessageBody` (#2546) --- .github/workflows/ci-master.yml | 66 ++++ .github/workflows/ci.yml | 62 ---- awc/CHANGES.md | 6 + awc/src/any_body.rs | 19 +- awc/src/connect.rs | 2 +- awc/src/frozen.rs | 6 +- awc/src/lib.rs | 5 +- awc/src/middleware/redirect.rs | 4 +- awc/src/request.rs | 74 ++-- awc/src/response.rs | 556 ----------------------------- awc/src/responses/json_body.rs | 192 ++++++++++ awc/src/responses/mod.rs | 49 +++ awc/src/responses/read_body.rs | 61 ++++ awc/src/responses/response.rs | 257 +++++++++++++ awc/src/responses/response_body.rs | 144 ++++++++ awc/src/sender.rs | 22 +- awc/src/ws.rs | 3 +- src/guard.rs | 12 +- 18 files changed, 853 insertions(+), 687 deletions(-) create mode 100644 .github/workflows/ci-master.yml delete mode 100644 awc/src/response.rs create mode 100644 awc/src/responses/json_body.rs create mode 100644 awc/src/responses/mod.rs create mode 100644 awc/src/responses/read_body.rs create mode 100644 awc/src/responses/response.rs create mode 100644 awc/src/responses/response_body.rs diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml new file mode 100644 index 000000000..548ec21b7 --- /dev/null +++ b/.github/workflows/ci-master.yml @@ -0,0 +1,66 @@ +name: CI (master only) + +on: + push: + branches: [master] + +jobs: + ci_feature_powerset_check: + name: Verify Feature Combinations + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Install cargo-hack + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + + - name: check feature combinations + uses: actions-rs/cargo@v1 + with: { command: ci-check-all-feature-powerset } + + - name: check feature combinations + uses: actions-rs/cargo@v1 + with: { command: ci-check-all-feature-powerset-linux } + + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9b98a7b8..fe464bf27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,68 +96,6 @@ jobs: cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean cargo-cache - ci_feature_powerset_check: - name: Verify Feature Combinations - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset } - - - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset-linux } - - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - - name: Generate coverage file - if: github.ref == 'refs/heads/master' - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - - name: Upload to Codecov - if: github.ref == 'refs/heads/master' - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } - rustdoc: name: doc tests runs-on: ubuntu-latest diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b5144b7a2..e1a059481 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,8 +3,14 @@ ## Unreleased - 2021-xx-xx - Rename `Connector::{ssl => openssl}`. [#2503] - Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546] +- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546] +- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546] [#2503]: https://github.com/actix/actix-web/pull/2503 +[#2546]: https://github.com/actix/actix-web/pull/2546 ## 3.0.0-beta.14 - 2021-12-17 diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs index 2ffeb5074..437216313 100644 --- a/awc/src/any_body.rs +++ b/awc/src/any_body.rs @@ -77,10 +77,27 @@ impl AnyBody where B: MessageBody + 'static, { + /// Converts a [`MessageBody`] type into the best possible representation. + /// + /// Checks size for `None` and tries to convert to `Bytes`. Otherwise, uses the `Body` variant. + pub fn from_message_body(body: B) -> Self + where + B: MessageBody, + { + if matches!(body.size(), BodySize::None) { + return Self::None; + } + + match body.try_into_bytes() { + Ok(body) => Self::Bytes { body }, + Err(body) => Self::new(body), + } + } + pub fn into_boxed(self) -> AnyBody { match self { Self::None => AnyBody::None, - Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes }, + Self::Bytes { body } => AnyBody::Bytes { body }, Self::Body { body } => AnyBody::new_boxed(body), } } diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 19870b069..f93014a67 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -16,7 +16,7 @@ use crate::{ client::{ Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, }, - response::ClientResponse, + ClientResponse, }; pub type BoxConnectorService = Rc< diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index cd93a1d60..b98d8d5e1 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -5,13 +5,13 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ + body::MessageBody, error::HttpError, header::{HeaderMap, HeaderName, TryIntoHeaderValue}, Method, RequestHead, Uri, }; use crate::{ - any_body::AnyBody, sender::{RequestSender, SendClientRequest}, BoxError, ClientConfig, }; @@ -46,7 +46,7 @@ impl FrozenClientRequest { /// Send a body. pub fn send_body(&self, body: B) -> SendClientRequest where - B: Into, + B: MessageBody + 'static, { RequestSender::Rc(self.head.clone(), None).send_body( self.addr, @@ -159,7 +159,7 @@ impl FrozenSendBuilder { /// Complete request construction and send a body. pub fn send_body(self, body: B) -> SendClientRequest where - B: Into, + B: MessageBody + 'static, { if let Some(e) = self.err { return e.into(); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 00c559406..cef8e03dc 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -113,7 +113,7 @@ pub mod error; mod frozen; pub mod middleware; mod request; -mod response; +mod responses; mod sender; pub mod test; pub mod ws; @@ -128,7 +128,8 @@ pub use self::client::Connector; pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; -pub use self::response::{ClientResponse, JsonBody, MessageBody}; +#[allow(deprecated)] +pub use self::responses::{ClientResponse, JsonBody, MessageBody, ResponseBody}; pub use self::sender::SendClientRequest; use std::{convert::TryFrom, rc::Rc, time::Duration}; diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 704d2d79d..0ee969eee 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -190,9 +190,7 @@ where let body_new = if is_redirect { // try to reuse body match body { - Some(ref bytes) => AnyBody::Bytes { - body: bytes.clone(), - }, + Some(ref bytes) => AnyBody::from(bytes.clone()), // TODO: should this be AnyBody::Empty or AnyBody::None. _ => AnyBody::empty(), } diff --git a/awc/src/request.rs b/awc/src/request.rs index 9e37b2755..3eb76e3f6 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -5,13 +5,13 @@ use futures_core::Stream; use serde::Serialize; use actix_http::{ + body::MessageBody, error::HttpError, header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair}, ConnectionType, Method, RequestHead, Uri, Version, }; use crate::{ - any_body::AnyBody, error::{FreezeRequestError, InvalidUrl}, frozen::FrozenClientRequest, sender::{PrepForSendingError, RequestSender, SendClientRequest}, @@ -26,20 +26,20 @@ use crate::cookie::{Cookie, CookieJar}; /// This type can be used to construct an instance of `ClientRequest` through a /// builder-like pattern. /// -/// ``` -/// #[actix_rt::main] -/// async fn main() { -/// let response = awc::Client::new() -/// .get("http://www.rust-lang.org") // <- Create request builder -/// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send HTTP request -/// .await; +/// ```no_run +/// # #[actix_rt::main] +/// # async fn main() { +/// let response = awc::Client::new() +/// .get("http://www.rust-lang.org") // <- Create request builder +/// .insert_header(("User-Agent", "Actix-web")) +/// .send() // <- Send HTTP request +/// .await; /// -/// response.and_then(|response| { // <- server HTTP response -/// println!("Response: {:?}", response); -/// Ok(()) -/// }); -/// } +/// response.and_then(|response| { // <- server HTTP response +/// println!("Response: {:?}", response); +/// Ok(()) +/// }); +/// # } /// ``` pub struct ClientRequest { pub(crate) head: RequestHead, @@ -174,17 +174,13 @@ impl ClientRequest { /// Append a header, keeping any that were set with an equivalent field name. /// - /// ``` - /// # #[actix_rt::main] - /// # async fn main() { - /// # use awc::Client; - /// use awc::http::header::CONTENT_TYPE; + /// ```no_run + /// use awc::{http::header, Client}; /// /// Client::new() /// .get("http://www.rust-lang.org") /// .insert_header(("X-TEST", "value")) - /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)); - /// # } + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); /// ``` pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { match header.try_into_pair() { @@ -252,23 +248,25 @@ impl ClientRequest { /// Set a cookie /// - /// ``` - /// #[actix_rt::main] - /// async fn main() { - /// let resp = awc::Client::new().get("https://www.rust-lang.org") - /// .cookie( - /// awc::cookie::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .send() - /// .await; + /// ```no_run + /// use awc::{cookie, Client}; /// - /// println!("Response: {:?}", resp); - /// } + /// # #[actix_rt::main] + /// # async fn main() { + /// let resp = Client::new().get("https://www.rust-lang.org") + /// .cookie( + /// awc::cookie::Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .send() + /// .await; + /// + /// println!("Response: {:?}", resp); + /// # } /// ``` #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { @@ -340,7 +338,7 @@ impl ClientRequest { /// Complete request construction and send body. pub fn send_body(self, body: B) -> SendClientRequest where - B: Into, + B: MessageBody + 'static, { let slf = match self.prep_for_sending() { Ok(slf) => slf, diff --git a/awc/src/response.rs b/awc/src/response.rs deleted file mode 100644 index 78cc339b4..000000000 --- a/awc/src/response.rs +++ /dev/null @@ -1,556 +0,0 @@ -use std::{ - cell::{Ref, RefMut}, - fmt, - future::Future, - io, - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, - time::{Duration, Instant}, -}; - -use actix_http::{ - error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions, - HttpMessage, Payload, ResponseHead, StatusCode, Version, -}; -use actix_rt::time::{sleep, Sleep}; -use bytes::{Bytes, BytesMut}; -use futures_core::{ready, Stream}; -use serde::de::DeserializeOwned; - -#[cfg(feature = "cookies")] -use crate::cookie::{Cookie, ParseError as CookieParseError}; -use crate::error::JsonPayloadError; - -/// Client Response -pub struct ClientResponse { - pub(crate) head: ResponseHead, - pub(crate) payload: Payload, - pub(crate) timeout: ResponseTimeout, -} - -/// helper enum with reusable sleep passed from `SendClientResponse`. -/// See `ClientResponse::_timeout` for reason. -pub(crate) enum ResponseTimeout { - Disabled(Option>>), - Enabled(Pin>), -} - -impl Default for ResponseTimeout { - fn default() -> Self { - Self::Disabled(None) - } -} - -impl ResponseTimeout { - fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> { - match *self { - Self::Enabled(ref mut timeout) => { - if timeout.as_mut().poll(cx).is_ready() { - Err(PayloadError::Io(io::Error::new( - io::ErrorKind::TimedOut, - "Response Payload IO timed out", - ))) - } else { - Ok(()) - } - } - Self::Disabled(_) => Ok(()), - } - } -} - -impl HttpMessage for ClientResponse { - type Stream = S; - - fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) - } - - fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions() - } - - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head.extensions_mut() - } -} - -impl ClientResponse { - /// Create new Request instance - pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { - ClientResponse { - head, - payload, - timeout: ResponseTimeout::default(), - } - } - - #[inline] - pub(crate) fn head(&self) -> &ResponseHead { - &self.head - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.head().version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.head().status - } - - #[inline] - /// Returns request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - /// Set a body and return previous body value - pub fn map_body(mut self, f: F) -> ClientResponse - where - F: FnOnce(&mut ResponseHead, Payload) -> Payload, - { - let payload = f(&mut self.head, self.payload); - - ClientResponse { - payload, - head: self.head, - timeout: self.timeout, - } - } - - /// Set a timeout duration for [`ClientResponse`](self::ClientResponse). - /// - /// This duration covers the duration of processing the response body stream - /// and would end it as timeout error when deadline met. - /// - /// Disabled by default. - pub fn timeout(self, dur: Duration) -> Self { - let timeout = match self.timeout { - ResponseTimeout::Disabled(Some(mut timeout)) - | ResponseTimeout::Enabled(mut timeout) => match Instant::now().checked_add(dur) { - Some(deadline) => { - timeout.as_mut().reset(deadline.into()); - ResponseTimeout::Enabled(timeout) - } - None => ResponseTimeout::Enabled(Box::pin(sleep(dur))), - }, - _ => ResponseTimeout::Enabled(Box::pin(sleep(dur))), - }; - - Self { - payload: self.payload, - head: self.head, - timeout, - } - } - - /// This method does not enable timeout. It's used to pass the boxed `Sleep` from - /// `SendClientRequest` and reuse it's heap allocation together with it's slot in - /// timer wheel. - pub(crate) fn _timeout(mut self, timeout: Option>>) -> Self { - self.timeout = ResponseTimeout::Disabled(timeout); - self - } - - /// Load request cookies. - #[cfg(feature = "cookies")] - pub fn cookies(&self) -> Result>>, CookieParseError> { - struct Cookies(Vec>); - - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(&header::SET_COOKIE) { - let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[cfg(feature = "cookies")] - pub fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } -} - -impl ClientResponse -where - S: Stream>, -{ - /// Loads HTTP response's body. - pub fn body(&mut self) -> MessageBody { - MessageBody::new(self) - } - - /// Loads and parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - pub fn json(&mut self) -> JsonBody { - JsonBody::new(self) - } -} - -impl Stream for ClientResponse -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.get_mut(); - this.timeout.poll_timeout(cx)?; - - Pin::new(&mut this.payload).poll_next(cx) - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; - -/// Future that resolves to a complete HTTP message body. -pub struct MessageBody { - length: Option, - timeout: ResponseTimeout, - body: Result, Option>, -} - -impl MessageBody -where - S: Stream>, -{ - /// Create `MessageBody` for request. - pub fn new(res: &mut ClientResponse) -> MessageBody { - let length = match res.headers().get(&header::CONTENT_LENGTH) { - Some(value) => { - let len = value.to_str().ok().and_then(|s| s.parse::().ok()); - - match len { - None => return Self::err(PayloadError::UnknownLength), - len => len, - } - } - None => None, - }; - - MessageBody { - length, - timeout: std::mem::take(&mut res.timeout), - body: Ok(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)), - } - } - - /// Change max size of payload. By default max size is 2048kB - pub fn limit(mut self, limit: usize) -> Self { - if let Ok(ref mut body) = self.body { - body.limit = limit; - } - self - } - - fn err(e: PayloadError) -> Self { - MessageBody { - length: None, - timeout: ResponseTimeout::default(), - body: Err(Some(e)), - } - } -} - -impl Future for MessageBody -where - S: Stream> + Unpin, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - match this.body { - Err(ref mut err) => Poll::Ready(Err(err.take().unwrap())), - Ok(ref mut body) => { - if let Some(len) = this.length.take() { - if len > body.limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - } - - this.timeout.poll_timeout(cx)?; - - Pin::new(body).poll(cx) - } - } - } -} - -/// Response's payload json parser, it resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// * content length is greater than 64k -pub struct JsonBody { - length: Option, - err: Option, - timeout: ResponseTimeout, - fut: Option>, - _phantom: PhantomData, -} - -impl JsonBody -where - S: Stream>, - U: DeserializeOwned, -{ - /// Create `JsonBody` for request. - pub fn new(res: &mut ClientResponse) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = res.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - } else { - false - }; - if !json { - return JsonBody { - length: None, - fut: None, - timeout: ResponseTimeout::default(), - err: Some(JsonPayloadError::ContentType), - _phantom: PhantomData, - }; - } - - let mut len = None; - - if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } - - JsonBody { - length: len, - err: None, - timeout: std::mem::take(&mut res.timeout), - fut: Some(ReadBody::new(res.take_payload(), 65536)), - _phantom: PhantomData, - } - } - - /// Change max size of payload. By default max size is 64kB - pub fn limit(mut self, limit: usize) -> Self { - if let Some(ref mut fut) = self.fut { - fut.limit = limit; - } - self - } -} - -impl Unpin for JsonBody -where - T: Stream> + Unpin, - U: DeserializeOwned, -{ -} - -impl Future for JsonBody -where - T: Stream> + Unpin, - U: DeserializeOwned, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = self.length.take() { - if len > self.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow))); - } - } - - self.timeout - .poll_timeout(cx) - .map_err(JsonPayloadError::Payload)?; - - let body = ready!(Pin::new(&mut self.get_mut().fut.as_mut().unwrap()).poll(cx))?; - Poll::Ready(serde_json::from_slice::(&body).map_err(JsonPayloadError::from)) - } -} - -struct ReadBody { - stream: Payload, - buf: BytesMut, - limit: usize, -} - -impl ReadBody { - fn new(stream: Payload, limit: usize) -> Self { - Self { - stream, - buf: BytesMut::new(), - limit, - } - } -} - -impl Future for ReadBody -where - S: Stream> + Unpin, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - while let Some(chunk) = ready!(Pin::new(&mut this.stream).poll_next(cx)?) { - if (this.buf.len() + chunk.len()) > this.limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - this.buf.extend_from_slice(&chunk); - } - - Poll::Ready(Ok(this.buf.split().freeze())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde::{Deserialize, Serialize}; - - use crate::{http::header, test::TestResponse}; - - #[actix_rt::test] - async fn test_body() { - let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish(); - match req.body().await.err().unwrap() { - PayloadError::UnknownLength => {} - _ => unreachable!("error"), - } - - let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish(); - match req.body().await.err().unwrap() { - PayloadError::Overflow => {} - _ => unreachable!("error"), - } - - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); - - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => {} - _ => unreachable!("error"), - } - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct MyObject { - name: String, - } - - fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { - match err { - JsonPayloadError::Payload(PayloadError::Overflow) => { - matches!(other, JsonPayloadError::Payload(PayloadError::Overflow)) - } - JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), - _ => false, - } - } - - #[actix_rt::test] - async fn test_json_body() { - let mut req = TestResponse::default().finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let mut req = TestResponse::default() - .insert_header(( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - )) - .finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let mut req = TestResponse::default() - .insert_header(( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - )) - .insert_header(( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - )) - .finish(); - - let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; - assert!(json_eq( - json.err().unwrap(), - JsonPayloadError::Payload(PayloadError::Overflow) - )); - - let mut req = TestResponse::default() - .insert_header(( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - )) - .insert_header(( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - )) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - } -} diff --git a/awc/src/responses/json_body.rs b/awc/src/responses/json_body.rs new file mode 100644 index 000000000..3912324b6 --- /dev/null +++ b/awc/src/responses/json_body.rs @@ -0,0 +1,192 @@ +use std::{ + future::Future, + marker::PhantomData, + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{error::PayloadError, header, HttpMessage}; +use bytes::Bytes; +use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; +use serde::de::DeserializeOwned; + +use super::{read_body::ReadBody, ResponseTimeout, DEFAULT_BODY_LIMIT}; +use crate::{error::JsonPayloadError, ClientResponse}; + +pin_project! { + /// A `Future` that reads a body stream, parses JSON, resolving to a deserialized `T`. + /// + /// # Errors + /// `Future` implementation returns error if: + /// - content type is not `application/json`; + /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB). + pub struct JsonBody { + #[pin] + body: Option>, + length: Option, + timeout: ResponseTimeout, + err: Option, + _phantom: PhantomData, + } +} + +impl JsonBody +where + S: Stream>, + T: DeserializeOwned, +{ + /// Creates a JSON body stream reader from a response by taking its payload. + pub fn new(res: &mut ClientResponse) -> Self { + // check content-type + let json = if let Ok(Some(mime)) = res.mime_type() { + mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + } else { + false + }; + + if !json { + return JsonBody { + length: None, + body: None, + timeout: ResponseTimeout::default(), + err: Some(JsonPayloadError::ContentType), + _phantom: PhantomData, + }; + } + + let length = res + .headers() + .get(&header::CONTENT_LENGTH) + .and_then(|len_hdr| len_hdr.to_str().ok()) + .and_then(|len_str| len_str.parse::().ok()); + + JsonBody { + body: Some(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)), + length, + timeout: mem::take(&mut res.timeout), + err: None, + _phantom: PhantomData, + } + } + + /// Change max size of payload. Default limit is 2 MiB. + pub fn limit(mut self, limit: usize) -> Self { + if let Some(ref mut fut) = self.body { + fut.limit = limit; + } + + self + } +} + +impl Future for JsonBody +where + S: Stream>, + T: DeserializeOwned, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + if let Some(err) = this.err.take() { + return Poll::Ready(Err(err)); + } + + if let Some(len) = this.length.take() { + let body = Option::as_ref(&this.body).unwrap(); + if len > body.limit { + return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow))); + } + } + + this.timeout + .poll_timeout(cx) + .map_err(JsonPayloadError::Payload)?; + + let body = ready!(this.body.as_pin_mut().unwrap().poll(cx))?; + Poll::Ready(serde_json::from_slice::(&body).map_err(JsonPayloadError::from)) + } +} + +#[cfg(test)] +mod tests { + use actix_http::BoxedPayloadStream; + use serde::{Deserialize, Serialize}; + use static_assertions::assert_impl_all; + + use super::*; + use crate::{http::header, test::TestResponse}; + + assert_impl_all!(JsonBody: Unpin); + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct MyObject { + name: String, + } + + fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { + match err { + JsonPayloadError::Payload(PayloadError::Overflow) => { + matches!(other, JsonPayloadError::Payload(PayloadError::Overflow)) + } + JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), + _ => false, + } + } + + #[actix_rt::test] + async fn read_json_body() { + let mut req = TestResponse::default().finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestResponse::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + )) + .finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestResponse::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + )) + .finish(); + + let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Payload(PayloadError::Overflow) + )); + + let mut req = TestResponse::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + } +} diff --git a/awc/src/responses/mod.rs b/awc/src/responses/mod.rs new file mode 100644 index 000000000..588ce014c --- /dev/null +++ b/awc/src/responses/mod.rs @@ -0,0 +1,49 @@ +use std::{future::Future, io, pin::Pin, task::Context}; + +use actix_http::error::PayloadError; +use actix_rt::time::Sleep; + +mod json_body; +mod read_body; +mod response; +mod response_body; + +pub use self::json_body::JsonBody; +pub use self::response::ClientResponse; +#[allow(deprecated)] +pub use self::response_body::{MessageBody, ResponseBody}; + +/// Default body size limit: 2 MiB +const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; + +/// Helper enum with reusable sleep passed from `SendClientResponse`. +/// +/// See [`ClientResponse::_timeout`] for reason. +pub(crate) enum ResponseTimeout { + Disabled(Option>>), + Enabled(Pin>), +} + +impl Default for ResponseTimeout { + fn default() -> Self { + Self::Disabled(None) + } +} + +impl ResponseTimeout { + fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> { + match *self { + Self::Enabled(ref mut timeout) => { + if timeout.as_mut().poll(cx).is_ready() { + Err(PayloadError::Io(io::Error::new( + io::ErrorKind::TimedOut, + "Response Payload IO timed out", + ))) + } else { + Ok(()) + } + } + Self::Disabled(_) => Ok(()), + } + } +} diff --git a/awc/src/responses/read_body.rs b/awc/src/responses/read_body.rs new file mode 100644 index 000000000..a32bbb984 --- /dev/null +++ b/awc/src/responses/read_body.rs @@ -0,0 +1,61 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{error::PayloadError, Payload}; +use bytes::{Bytes, BytesMut}; +use futures_core::{ready, Stream}; +use pin_project_lite::pin_project; + +pin_project! { + pub(crate) struct ReadBody { + #[pin] + pub(crate) stream: Payload, + pub(crate) buf: BytesMut, + pub(crate) limit: usize, + } +} + +impl ReadBody { + pub(crate) fn new(stream: Payload, limit: usize) -> Self { + Self { + stream, + buf: BytesMut::new(), + limit, + } + } +} + +impl Future for ReadBody +where + S: Stream>, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + while let Some(chunk) = ready!(this.stream.as_mut().poll_next(cx)?) { + if (this.buf.len() + chunk.len()) > *this.limit { + return Poll::Ready(Err(PayloadError::Overflow)); + } + + this.buf.extend_from_slice(&chunk); + } + + Poll::Ready(Ok(this.buf.split().freeze())) + } +} + +#[cfg(test)] +mod tests { + use static_assertions::assert_impl_all; + + use super::*; + use crate::any_body::AnyBody; + + assert_impl_all!(ReadBody<()>: Unpin); + assert_impl_all!(ReadBody: Unpin); +} diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs new file mode 100644 index 000000000..6385aea19 --- /dev/null +++ b/awc/src/responses/response.rs @@ -0,0 +1,257 @@ +use std::{ + cell::{Ref, RefMut}, + fmt, mem, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, +}; + +use actix_http::{ + error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions, + HttpMessage, Payload, ResponseHead, StatusCode, Version, +}; +use actix_rt::time::{sleep, Sleep}; +use bytes::Bytes; +use futures_core::Stream; +use pin_project_lite::pin_project; +use serde::de::DeserializeOwned; + +#[cfg(feature = "cookies")] +use crate::cookie::{Cookie, ParseError as CookieParseError}; + +use super::{JsonBody, ResponseBody, ResponseTimeout}; + +pin_project! { + /// Client Response + pub struct ClientResponse { + pub(crate) head: ResponseHead, + #[pin] + pub(crate) payload: Payload, + pub(crate) timeout: ResponseTimeout, + } +} + +impl ClientResponse { + /// Create new Request instance + pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { + ClientResponse { + head, + payload, + timeout: ResponseTimeout::default(), + } + } + + #[inline] + pub(crate) fn head(&self) -> &ResponseHead { + &self.head + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.head().version + } + + /// Get the status from the server. + #[inline] + pub fn status(&self) -> StatusCode { + self.head().status + } + + #[inline] + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + /// Set a body and return previous body value + pub fn map_body(mut self, f: F) -> ClientResponse + where + F: FnOnce(&mut ResponseHead, Payload) -> Payload, + { + let payload = f(&mut self.head, self.payload); + + ClientResponse { + payload, + head: self.head, + timeout: self.timeout, + } + } + + /// Set a timeout duration for [`ClientResponse`](self::ClientResponse). + /// + /// This duration covers the duration of processing the response body stream + /// and would end it as timeout error when deadline met. + /// + /// Disabled by default. + pub fn timeout(self, dur: Duration) -> Self { + let timeout = match self.timeout { + ResponseTimeout::Disabled(Some(mut timeout)) + | ResponseTimeout::Enabled(mut timeout) => match Instant::now().checked_add(dur) { + Some(deadline) => { + timeout.as_mut().reset(deadline.into()); + ResponseTimeout::Enabled(timeout) + } + None => ResponseTimeout::Enabled(Box::pin(sleep(dur))), + }, + _ => ResponseTimeout::Enabled(Box::pin(sleep(dur))), + }; + + Self { + payload: self.payload, + head: self.head, + timeout, + } + } + + /// This method does not enable timeout. It's used to pass the boxed `Sleep` from + /// `SendClientRequest` and reuse it's heap allocation together with it's slot in + /// timer wheel. + pub(crate) fn _timeout(mut self, timeout: Option>>) -> Self { + self.timeout = ResponseTimeout::Disabled(timeout); + self + } + + /// Load request cookies. + #[cfg(feature = "cookies")] + pub fn cookies(&self) -> Result>>, CookieParseError> { + struct Cookies(Vec>); + + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(&header::SET_COOKIE) { + let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + cookies.push(Cookie::parse_encoded(s)?.into_owned()); + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + #[cfg(feature = "cookies")] + pub fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } +} + +impl ClientResponse +where + S: Stream>, +{ + /// Returns a [`Future`] that consumes the body stream and resolves to [`Bytes`]. + /// + /// # Errors + /// `Future` implementation returns error if: + /// - content type is not `application/json` + /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB) + /// + /// # Examples + /// ```no_run + /// # use awc::Client; + /// # use bytes::Bytes; + /// # #[actix_rt::main] + /// # async fn async_ctx() -> Result<(), Box> { + /// let client = Client::default(); + /// let mut res = client.get("https://httpbin.org/robots.txt").send().await?; + /// let body: Bytes = res.body().await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`Future`]: std::future::Future + pub fn body(&mut self) -> ResponseBody { + ResponseBody::new(self) + } + + /// Returns a [`Future`] consumes the body stream, parses JSON, and resolves to a deserialized + /// `T` value. + /// + /// # Errors + /// Future returns error if: + /// - content type is not `application/json`; + /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB). + /// + /// # Examples + /// ```no_run + /// # use awc::Client; + /// # #[actix_rt::main] + /// # async fn async_ctx() -> Result<(), Box> { + /// let client = Client::default(); + /// let mut res = client.get("https://httpbin.org/json").send().await?; + /// let val = res.json::().await?; + /// assert!(val.is_object()); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`Future`]: std::future::Future + pub fn json(&mut self) -> JsonBody { + JsonBody::new(self) + } +} + +impl fmt::Debug for ClientResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + +impl HttpMessage for ClientResponse { + type Stream = S; + + fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + fn take_payload(&mut self) -> Payload { + mem::replace(&mut self.payload, Payload::None) + } + + fn extensions(&self) -> Ref<'_, Extensions> { + self.head.extensions() + } + + fn extensions_mut(&self) -> RefMut<'_, Extensions> { + self.head.extensions_mut() + } +} + +impl Stream for ClientResponse +where + S: Stream> + Unpin, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.timeout.poll_timeout(cx)?; + this.payload.poll_next(cx) + } +} + +#[cfg(test)] +mod tests { + use static_assertions::assert_impl_all; + + use super::*; + use crate::any_body::AnyBody; + + assert_impl_all!(ClientResponse: Unpin); + assert_impl_all!(ClientResponse<()>: Unpin); + assert_impl_all!(ClientResponse: Unpin); +} diff --git a/awc/src/responses/response_body.rs b/awc/src/responses/response_body.rs new file mode 100644 index 000000000..8d9d1274a --- /dev/null +++ b/awc/src/responses/response_body.rs @@ -0,0 +1,144 @@ +use std::{ + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{error::PayloadError, header, HttpMessage}; +use bytes::Bytes; +use futures_core::Stream; +use pin_project_lite::pin_project; + +use super::{read_body::ReadBody, ResponseTimeout, DEFAULT_BODY_LIMIT}; +use crate::ClientResponse; + +pin_project! { + /// A `Future` that reads a body stream, resolving as [`Bytes`]. + /// + /// # Errors + /// `Future` implementation returns error if: + /// - content type is not `application/json`; + /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB). + pub struct ResponseBody { + #[pin] + body: Option>, + length: Option, + timeout: ResponseTimeout, + err: Option, + } +} + +#[deprecated(since = "3.0.0", note = "Renamed to `ResponseBody`.")] +pub type MessageBody = ResponseBody; + +impl ResponseBody +where + S: Stream>, +{ + /// Creates a body stream reader from a response by taking its payload. + pub fn new(res: &mut ClientResponse) -> ResponseBody { + let length = match res.headers().get(&header::CONTENT_LENGTH) { + Some(value) => { + let len = value.to_str().ok().and_then(|s| s.parse::().ok()); + + match len { + None => return Self::err(PayloadError::UnknownLength), + len => len, + } + } + None => None, + }; + + ResponseBody { + body: Some(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)), + length, + timeout: mem::take(&mut res.timeout), + err: None, + } + } + + /// Change max size limit of payload. + /// + /// The default limit is 2 MiB. + pub fn limit(mut self, limit: usize) -> Self { + if let Some(ref mut body) = self.body { + body.limit = limit; + } + + self + } + + fn err(err: PayloadError) -> Self { + ResponseBody { + body: None, + length: None, + timeout: ResponseTimeout::default(), + err: Some(err), + } + } +} + +impl Future for ResponseBody +where + S: Stream>, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + if let Some(err) = this.err.take() { + return Poll::Ready(Err(err)); + } + + if let Some(len) = this.length.take() { + let body = Option::as_ref(&this.body).unwrap(); + if len > body.limit { + return Poll::Ready(Err(PayloadError::Overflow)); + } + } + + this.timeout.poll_timeout(cx)?; + + this.body.as_pin_mut().unwrap().poll(cx) + } +} + +#[cfg(test)] +mod tests { + use static_assertions::assert_impl_all; + + use super::*; + use crate::{http::header, test::TestResponse}; + + assert_impl_all!(ResponseBody<()>: Unpin); + + #[actix_rt::test] + async fn read_body() { + let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish(); + match req.body().await.err().unwrap() { + PayloadError::UnknownLength => {} + _ => unreachable!("error"), + } + + let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish(); + match req.body().await.err().unwrap() { + PayloadError::Overflow => {} + _ => unreachable!("error"), + } + + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); + assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); + + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); + match req.body().limit(5).await.err().unwrap() { + PayloadError::Overflow => {} + _ => unreachable!("error"), + } + } +} diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 29c814531..71d705d38 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_http::{ - body::BodyStream, + body::{BodyStream, MessageBody}, error::HttpError, header::{self, HeaderMap, HeaderName, TryIntoHeaderValue}, RequestHead, RequestHeadType, @@ -189,15 +189,17 @@ impl RequestSender { body: B, ) -> SendClientRequest where - B: Into, + B: MessageBody + 'static, { let req = match self { - RequestSender::Owned(head) => { - ConnectRequest::Client(RequestHeadType::Owned(head), body.into(), addr) - } + RequestSender::Owned(head) => ConnectRequest::Client( + RequestHeadType::Owned(head), + AnyBody::from_message_body(body).into_boxed(), + addr, + ), RequestSender::Rc(head, extra_headers) => ConnectRequest::Client( RequestHeadType::Rc(head, extra_headers), - body.into(), + AnyBody::from_message_body(body).into_boxed(), addr, ), }; @@ -229,9 +231,7 @@ impl RequestSender { response_decompress, timeout, config, - AnyBody::Bytes { - body: Bytes::from(body), - }, + AnyBody::from_message_body(body.into_bytes()), ) } @@ -260,9 +260,7 @@ impl RequestSender { response_decompress, timeout, config, - AnyBody::Bytes { - body: Bytes::from(body), - }, + AnyBody::from_message_body(body.into_bytes()), ) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 06d54aadb..c63e22969 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -42,8 +42,7 @@ use crate::{ header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}, ConnectionType, Method, StatusCode, Uri, Version, }, - response::ClientResponse, - ClientConfig, + ClientConfig, ClientResponse, }; #[cfg(feature = "cookies")] diff --git a/src/guard.rs b/src/guard.rs index a5770df89..d5c585c1b 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -270,13 +270,11 @@ impl Guard for HeaderGuard { /// ``` /// use actix_web::{web, guard::Host, App, HttpResponse}; /// -/// fn main() { -/// App::new().service( -/// web::resource("/index.html") -/// .guard(Host("www.rust-lang.org")) -/// .to(|| HttpResponse::MethodNotAllowed()) -/// ); -/// } +/// App::new().service( +/// web::resource("/index.html") +/// .guard(Host("www.rust-lang.org")) +/// .to(|| HttpResponse::MethodNotAllowed()) +/// ); /// ``` pub fn Host>(host: H) -> HostGuard { HostGuard(host.as_ref().to_string(), None) From 3756dfc2cea2049c393e2944cffeb84075a982e4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 02:23:22 +0000 Subject: [PATCH 216/861] move client to own module --- awc/src/builder.rs | 6 +- awc/src/client/mod.rs | 188 ++++++++++++++++++++++++++++++++++++++++-- awc/src/frozen.rs | 3 +- awc/src/lib.rs | 183 +--------------------------------------- awc/src/request.rs | 22 ++--- awc/src/sender.rs | 3 +- awc/src/ws.rs | 5 +- 7 files changed, 203 insertions(+), 207 deletions(-) diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 30f203bb8..16a4e9cb5 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -9,11 +9,13 @@ use actix_rt::net::{ActixStream, TcpStream}; use actix_service::{boxed, Service}; use crate::{ - client::{ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection}, + client::{ + ClientConfig, ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection, + }, connect::DefaultConnector, error::SendRequestError, middleware::{NestTransform, Redirect, Transform}, - Client, ClientConfig, ConnectRequest, ConnectResponse, + Client, ConnectRequest, ConnectResponse, }; /// An HTTP Client builder diff --git a/awc/src/client/mod.rs b/awc/src/client/mod.rs index 0d5c899bc..d5854d83e 100644 --- a/awc/src/client/mod.rs +++ b/awc/src/client/mod.rs @@ -1,6 +1,15 @@ //! HTTP client. -use http::Uri; +use std::{convert::TryFrom, rc::Rc, time::Duration}; + +use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri}; +use actix_rt::net::TcpStream; +use actix_service::Service; +pub use actix_tls::connect::{ + ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection, +}; + +use crate::{ws, BoxConnectorService, ClientBuilder, ClientRequest}; mod config; mod connection; @@ -10,10 +19,6 @@ mod h1proto; mod h2proto; mod pool; -pub use actix_tls::connect::{ - ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection, -}; - pub use self::connection::{Connection, ConnectionIo}; pub use self::connector::{Connector, ConnectorService}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; @@ -23,3 +28,176 @@ pub struct Connect { pub uri: Uri, pub addr: Option, } + +/// An asynchronous HTTP and WebSocket client. +/// +/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU +/// and memory usage. +/// +/// # Examples +/// ``` +/// use awc::Client; +/// +/// #[actix_rt::main] +/// async fn main() { +/// let mut client = Client::default(); +/// +/// let res = client.get("http://www.rust-lang.org") +/// .insert_header(("User-Agent", "my-app/1.2")) +/// .send() +/// .await; +/// +/// println!("Response: {:?}", res); +/// } +/// ``` +#[derive(Clone)] +pub struct Client(pub(crate) ClientConfig); + +#[derive(Clone)] +pub(crate) struct ClientConfig { + pub(crate) connector: BoxConnectorService, + pub(crate) default_headers: Rc, + pub(crate) timeout: Option, +} + +impl Default for Client { + fn default() -> Self { + ClientBuilder::new().finish() + } +} + +impl Client { + /// Create new client instance with default settings. + pub fn new() -> Client { + Client::default() + } + + /// Create `Client` builder. + /// This function is equivalent of `ClientBuilder::new()`. + pub fn builder() -> ClientBuilder< + impl Service< + ConnectInfo, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone, + > { + ClientBuilder::new() + } + + /// Construct HTTP request. + pub fn request(&self, method: Method, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + let mut req = ClientRequest::new(method, url, self.0.clone()); + + for header in self.0.default_headers.iter() { + // header map is empty + // TODO: probably append instead + req = req.insert_header_if_none(header); + } + req + } + + /// Create `ClientRequest` from `RequestHead` + /// + /// It is useful for proxy requests. This implementation + /// copies all headers and the method. + pub fn request_from(&self, url: U, head: &RequestHead) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + let mut req = self.request(head.method.clone(), url); + for header in head.headers.iter() { + req = req.insert_header_if_none(header); + } + req + } + + /// Construct HTTP *GET* request. + pub fn get(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::GET, url) + } + + /// Construct HTTP *HEAD* request. + pub fn head(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::HEAD, url) + } + + /// Construct HTTP *PUT* request. + pub fn put(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::PUT, url) + } + + /// Construct HTTP *POST* request. + pub fn post(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::POST, url) + } + + /// Construct HTTP *PATCH* request. + pub fn patch(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::PATCH, url) + } + + /// Construct HTTP *DELETE* request. + pub fn delete(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::DELETE, url) + } + + /// Construct HTTP *OPTIONS* request. + pub fn options(&self, url: U) -> ClientRequest + where + Uri: TryFrom, + >::Error: Into, + { + self.request(Method::OPTIONS, url) + } + + /// Initialize a WebSocket connection. + /// Returns a WebSocket connection builder. + pub fn ws(&self, url: U) -> ws::WebsocketsRequest + where + Uri: TryFrom, + >::Error: Into, + { + let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); + for (key, value) in self.0.default_headers.iter() { + req.head.headers.insert(key.clone(), value.clone()); + } + req + } + + /// Get default HeaderMap of Client. + /// + /// Returns Some(&mut HeaderMap) when Client object is unique + /// (No other clone of client exists at the same time). + pub fn headers(&mut self) -> Option<&mut HeaderMap> { + Rc::get_mut(&mut self.0.default_headers) + } +} diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index b98d8d5e1..14ecf9f32 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -12,8 +12,9 @@ use actix_http::{ }; use crate::{ + client::ClientConfig, sender::{RequestSender, SendClientRequest}, - BoxError, ClientConfig, + BoxError, }; /// `FrozenClientRequest` struct represents cloneable client request. diff --git a/awc/src/lib.rs b/awc/src/lib.rs index cef8e03dc..348d9312b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -124,7 +124,7 @@ pub use actix_http as http; pub use cookie; pub use self::builder::ClientBuilder; -pub use self::client::Connector; +pub use self::client::{Client, Connector}; pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; @@ -132,185 +132,4 @@ pub use self::request::ClientRequest; pub use self::responses::{ClientResponse, JsonBody, MessageBody, ResponseBody}; pub use self::sender::SendClientRequest; -use std::{convert::TryFrom, rc::Rc, time::Duration}; - -use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri}; -use actix_rt::net::TcpStream; -use actix_service::Service; - -use self::client::{ConnectInfo, TcpConnectError, TcpConnection}; - pub(crate) type BoxError = Box; - -/// An asynchronous HTTP and WebSocket client. -/// -/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU -/// and memory usage. -/// -/// # Examples -/// ``` -/// use awc::Client; -/// -/// #[actix_rt::main] -/// async fn main() { -/// let mut client = Client::default(); -/// -/// let res = client.get("http://www.rust-lang.org") -/// .insert_header(("User-Agent", "my-app/1.2")) -/// .send() -/// .await; -/// -/// println!("Response: {:?}", res); -/// } -/// ``` -#[derive(Clone)] -pub struct Client(ClientConfig); - -#[derive(Clone)] -pub(crate) struct ClientConfig { - pub(crate) connector: BoxConnectorService, - pub(crate) default_headers: Rc, - pub(crate) timeout: Option, -} - -impl Default for Client { - fn default() -> Self { - ClientBuilder::new().finish() - } -} - -impl Client { - /// Create new client instance with default settings. - pub fn new() -> Client { - Client::default() - } - - /// Create `Client` builder. - /// This function is equivalent of `ClientBuilder::new()`. - pub fn builder() -> ClientBuilder< - impl Service< - ConnectInfo, - Response = TcpConnection, - Error = TcpConnectError, - > + Clone, - > { - ClientBuilder::new() - } - - /// Construct HTTP request. - pub fn request(&self, method: Method, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = ClientRequest::new(method, url, self.0.clone()); - - for header in self.0.default_headers.iter() { - // header map is empty - // TODO: probably append instead - req = req.insert_header_if_none(header); - } - req - } - - /// Create `ClientRequest` from `RequestHead` - /// - /// It is useful for proxy requests. This implementation - /// copies all headers and the method. - pub fn request_from(&self, url: U, head: &RequestHead) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = self.request(head.method.clone(), url); - for header in head.headers.iter() { - req = req.insert_header_if_none(header); - } - req - } - - /// Construct HTTP *GET* request. - pub fn get(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::GET, url) - } - - /// Construct HTTP *HEAD* request. - pub fn head(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::HEAD, url) - } - - /// Construct HTTP *PUT* request. - pub fn put(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::PUT, url) - } - - /// Construct HTTP *POST* request. - pub fn post(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::POST, url) - } - - /// Construct HTTP *PATCH* request. - pub fn patch(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::PATCH, url) - } - - /// Construct HTTP *DELETE* request. - pub fn delete(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::DELETE, url) - } - - /// Construct HTTP *OPTIONS* request. - pub fn options(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::OPTIONS, url) - } - - /// Initialize a WebSocket connection. - /// Returns a WebSocket connection builder. - pub fn ws(&self, url: U) -> ws::WebsocketsRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in self.0.default_headers.iter() { - req.head.headers.insert(key.clone(), value.clone()); - } - req - } - - /// Get default HeaderMap of Client. - /// - /// Returns Some(&mut HeaderMap) when Client object is unique - /// (No other clone of client exists at the same time). - pub fn headers(&mut self) -> Option<&mut HeaderMap> { - Rc::get_mut(&mut self.0.default_headers) - } -} diff --git a/awc/src/request.rs b/awc/src/request.rs index 3eb76e3f6..8824dd08a 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -12,10 +12,11 @@ use actix_http::{ }; use crate::{ + client::ClientConfig, error::{FreezeRequestError, InvalidUrl}, frozen::FrozenClientRequest, sender::{PrepForSendingError, RequestSender, SendClientRequest}, - BoxError, ClientConfig, + BoxError, }; #[cfg(feature = "cookies")] @@ -249,23 +250,16 @@ impl ClientRequest { /// Set a cookie /// /// ```no_run - /// use awc::{cookie, Client}; + /// use awc::{cookie::Cookie, Client}; /// /// # #[actix_rt::main] /// # async fn main() { - /// let resp = Client::new().get("https://www.rust-lang.org") - /// .cookie( - /// awc::cookie::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .send() - /// .await; + /// let res = Client::new().get("https://httpbin.org/cookies") + /// .cookie(Cookie::new("name", "value")) + /// .send() + /// .await; /// - /// println!("Response: {:?}", resp); + /// println!("Response: {:?}", res); /// # } /// ``` #[cfg(feature = "cookies")] diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 71d705d38..edf41163d 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -24,8 +24,9 @@ use actix_http::{encoding::Decoder, header::ContentEncoding, Payload}; use crate::{ any_body::AnyBody, + client::ClientConfig, error::{FreezeRequestError, InvalidUrl, SendRequestError}, - BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse, + BoxError, ClientResponse, ConnectRequest, ConnectResponse, }; #[derive(Debug, From)] diff --git a/awc/src/ws.rs b/awc/src/ws.rs index c63e22969..96f8cf893 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -31,18 +31,19 @@ use std::{convert::TryFrom, fmt, net::SocketAddr, str}; use actix_codec::Framed; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; -use actix_service::Service; +use actix_service::Service as _; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::{ + client::ClientConfig, connect::{BoxedSocket, ConnectRequest}, error::{HttpError, InvalidUrl, SendRequestError, WsClientError}, http::{ header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}, ConnectionType, Method, StatusCode, Uri, Version, }, - ClientConfig, ClientResponse, + ClientResponse, }; #[cfg(feature = "cookies")] From 01cbfc57244bc7c0528d158dbc61492ed65a64a3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 02:28:23 +0000 Subject: [PATCH 217/861] reduce -http re-exports in awc --- awc/src/error.rs | 1 + awc/src/lib.rs | 17 +++++++++++++---- src/http/mod.rs | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/awc/src/error.rs b/awc/src/error.rs index c1d855053..aa9dc4d99 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,5 +1,6 @@ //! HTTP client errors +// TODO: figure out how best to expose http::Error vs actix_http::Error pub use actix_http::{ error::{HttpError, PayloadError}, header::HeaderValue, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 348d9312b..970ca2d92 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,6 +105,11 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +pub use actix_http::body; + +#[cfg(feature = "cookies")] +pub use cookie; + mod any_body; mod builder; mod client; @@ -118,10 +123,14 @@ mod sender; pub mod test; pub mod ws; -// TODO: hmmmmmm -pub use actix_http as http; -#[cfg(feature = "cookies")] -pub use cookie; +pub mod http { + //! Various HTTP related types. + + // TODO: figure out how best to expose http::Error vs actix_http::Error + pub use actix_http::{ + header, uri, ConnectionType, Error, Method, StatusCode, Uri, Version, + }; +} pub use self::builder::ClientBuilder; pub use self::client::{Client, Connector}; diff --git a/src/http/mod.rs b/src/http/mod.rs index bbd94a60f..2581532cd 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -2,4 +2,5 @@ pub mod header; +// TODO: figure out how best to expose http::Error vs actix_http::Error pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version}; From 34e5c7c799b88dcd18ebaf8c4b8d75132e19bf25 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 24 Dec 2021 21:35:19 -0500 Subject: [PATCH 218/861] Improve module docs for error handler middleware (#2543) --- src/middleware/err_handlers.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 6d064372f..bde054330 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -37,27 +37,21 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result(mut res: dev::ServiceResponse) -> Result> { -/// res.response_mut() -/// .headers_mut() -/// .insert(header::CONTENT_TYPE, header::HeaderValue::from_static("Error")); +/// use actix_web::http::{header, StatusCode}; +/// use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; +/// use actix_web::{dev, web, App, HttpResponse, Result}; /// +/// fn add_error_header(mut res: dev::ServiceResponse) -> Result> { +/// res.response_mut().headers_mut().insert( +/// header::CONTENT_TYPE, +/// header::HeaderValue::from_static("Error"), +/// ); /// Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) /// } /// /// let app = App::new() -/// .wrap( -/// ErrorHandlers::new() -/// .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .service(web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()) -/// )); +/// .wrap(ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, add_error_header)) +/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError))); /// ``` pub struct ErrorHandlers { handlers: Handlers, From adf993584124f44fc07835fcd7e467184291ab38 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 03:44:09 +0000 Subject: [PATCH 219/861] improve scope documentation closes #2389 --- actix-router/src/resource.rs | 54 +++++++-------- src/resource.rs | 27 ++++---- src/route.rs | 6 +- src/scope.rs | 126 ++++++++++++++++------------------- src/web.rs | 13 +++- 5 files changed, 109 insertions(+), 117 deletions(-) diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index fa77b1e7b..f1eb9caf5 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -29,26 +29,25 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// /// /// # Pattern Format and Matching Behavior -/// /// Resource pattern is defined as a string of zero or more _segments_ where each segment is /// preceded by a slash `/`. /// -/// This means that pattern string __must__ either be empty or begin with a slash (`/`). -/// This also implies that a trailing slash in pattern defines an empty segment. -/// For example, the pattern `"/user/"` has two segments: `["user", ""]` +/// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also +/// implies that a trailing slash in pattern defines an empty segment. For example, the pattern +/// `"/user/"` has two segments: `["user", ""]` /// -/// A key point to underhand is that `ResourceDef` matches segments, not strings. -/// It matches segments individually. -/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`, -/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`. +/// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are +/// matched individually. For example, the pattern `/user/` is not considered a prefix for the path +/// `/user/123/456`, because the second segment doesn't match: `["user", ""]` +/// vs `["user", "123", "456"]`. /// /// This definition is consistent with the definition of absolute URL path in -/// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) +/// [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) /// /// /// # Static Resources -/// A static resource is the most basic type of definition. Pass a pattern to -/// [new][Self::new]. Conforming paths must match the pattern exactly. +/// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new]. +/// Conforming paths must match the pattern exactly. /// /// ## Examples /// ``` @@ -63,7 +62,6 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(!resource.is_match("/search")); /// ``` /// -/// /// # Dynamic Segments /// Also known as "path parameters". Resources can define sections of a pattern that be extracted /// from a conforming path, if it conforms to (one of) the resource pattern(s). @@ -102,15 +100,15 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert_eq!(path.get("id").unwrap(), "123"); /// ``` /// -/// /// # Prefix Resources /// A prefix resource is defined as pattern that can match just the start of a path, up to a /// segment boundary. /// /// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior. -/// They define and therefore require an empty segment in order to match. Examples are given below. +/// They define and therefore require an empty segment in order to match. It is easier to understand +/// this behavior after reading the [matching behavior section]. Examples are given below. /// -/// Empty pattern matches any path as a prefix. +/// The empty pattern (`""`), as a prefix, matches any path. /// /// Prefix resources can contain dynamic segments. /// @@ -130,7 +128,6 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(!resource.is_match("/user/123")); /// ``` /// -/// /// # Custom Regex Segments /// Dynamic segments can be customised to only match a specific regular expression. It can be /// helpful to do this if resource definitions would otherwise conflict and cause one to @@ -158,7 +155,6 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(!resource.is_match("/user/abc")); /// ``` /// -/// /// # Tail Segments /// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those /// up until a `/` character), there is a special pattern to match (and capture) the remaining @@ -179,7 +175,6 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert_eq!(path.get("tail").unwrap(), "main/LICENSE"); /// ``` /// -/// /// # Multi-Pattern Resources /// For resources that can map to multiple distinct paths, it may be suitable to use /// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined @@ -198,7 +193,6 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(resource.is_match("/index")); /// ``` /// -/// /// # Trailing Slashes /// It should be noted that this library takes no steps to normalize intra-path or trailing slashes. /// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if @@ -212,6 +206,8 @@ const REGEX_FLAGS: &str = "(?s-m)"; /// assert!(!ResourceDef::new("/root/").is_match("/root")); /// assert!(!ResourceDef::prefix("/root/").is_match("/root")); /// ``` +/// +/// [matching behavior section]: #pattern-format-and-matching-behavior #[derive(Clone, Debug)] pub struct ResourceDef { id: u16, @@ -279,7 +275,7 @@ impl ResourceDef { /// ``` pub fn new(paths: T) -> Self { profile_method!(new); - Self::new2(paths, false) + Self::construct(paths, false) } /// Constructs a new resource definition using a pattern that performs prefix matching. @@ -292,7 +288,7 @@ impl ResourceDef { /// resource definition with a tail segment; use [`new`][Self::new] in this case. /// /// # Panics - /// Panics if path regex pattern is malformed. + /// Panics if path pattern is malformed. /// /// # Examples /// ``` @@ -307,14 +303,14 @@ impl ResourceDef { /// ``` pub fn prefix(paths: T) -> Self { profile_method!(prefix); - ResourceDef::new2(paths, true) + ResourceDef::construct(paths, true) } /// Constructs a new resource definition using a string pattern that performs prefix matching, - /// inserting a `/` to beginning of the pattern if absent and pattern is not empty. + /// ensuring a leading `/` if pattern is not empty. /// /// # Panics - /// Panics if path regex pattern is malformed. + /// Panics if path pattern is malformed. /// /// # Examples /// ``` @@ -515,8 +511,8 @@ impl ResourceDef { .collect::>(); match patterns.len() { - 1 => ResourceDef::new2(&patterns[0], other.is_prefix()), - _ => ResourceDef::new2(patterns, other.is_prefix()), + 1 => ResourceDef::construct(&patterns[0], other.is_prefix()), + _ => ResourceDef::construct(patterns, other.is_prefix()), } } @@ -881,8 +877,8 @@ impl ResourceDef { } } - fn new2(paths: T, is_prefix: bool) -> Self { - profile_method!(new2); + fn construct(paths: T, is_prefix: bool) -> Self { + profile_method!(construct); let patterns = paths.patterns(); let (pat_type, segments) = match &patterns { @@ -1814,7 +1810,7 @@ mod tests { #[test] #[should_panic] - fn prefix_plus_tail_match_is_allowed() { + fn prefix_plus_tail_match_disallowed() { ResourceDef::prefix("/user/{id}*"); } } diff --git a/src/resource.rs b/src/resource.rs index d94d2a464..0d82bb004 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -23,28 +23,25 @@ use crate::{ BoxError, Error, FromRequest, HttpResponse, Responder, }; -/// *Resource* is an entry in resources table which corresponds to requested URL. +/// A collection of [`Route`]s that respond to the same path pattern. /// -/// Resource in turn has at least one route. -/// Route consists of an handlers objects and list of guards -/// (objects that implement `Guard` trait). -/// Resources and routes uses builder-like pattern for configuration. -/// During request handling, resource object iterate through all routes -/// and check guards for specific route, if request matches all -/// guards, route considered matched and route handler get called. +/// Resource in turn has at least one route. Route consists of an handlers objects and list of +/// guards (objects that implement `Guard` trait). Resources and routes uses builder-like pattern +/// for configuration. During request handling, resource object iterate through all routes and check +/// guards for specific route, if request matches all guards, route considered matched and route +/// handler get called. /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/") -/// .route(web::get().to(|| HttpResponse::Ok()))); -/// } +/// let app = App::new().service( +/// web::resource("/") +/// .route(web::get().to(|| HttpResponse::Ok()))); /// ``` /// -/// If no matching route could be found, *405* response code get returned. -/// Default behavior could be overridden with `default_resource()` method. +/// If no matching route could be found, *405* response code get returned. Default behavior could be +/// overridden with `default_resource()` method. pub struct Resource { endpoint: T, rdef: Patterns, diff --git a/src/route.rs b/src/route.rs index 4447bff50..16c01275b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -15,10 +15,10 @@ use crate::{ BoxError, Error, FromRequest, HttpResponse, Responder, }; -/// Resource route definition +/// A request handler with [guards](guard). /// -/// Route uses builder-like pattern for configuration. -/// If handler is not explicitly set, default *404 Not Found* handler is used. +/// Route uses a builder-like pattern for configuration. If handler is not set, a `404 Not Found` +/// handler is used. pub struct Route { service: BoxedHttpServiceFactory, guards: Rc>>, diff --git a/src/scope.rs b/src/scope.rs index 1fd282f61..c3bab8f7e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -27,34 +27,36 @@ use crate::{ type Guards = Vec>; -/// Resources scope. +/// A collection of [`Route`]s, [`Resource`]s, or other services that share a common path prefix. /// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. +/// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from +/// requests using the [`Path`](crate::web::Path) extractor or +/// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info). /// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. +/// # Avoid Trailing Slashes +/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost +/// certainly not have the expected behavior. See the [documentation on resource definitions][pat] +/// to understand why this is the case and how to correctly construct scope/prefix definitions. /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new().service( -/// web::scope("/{project_id}/") -/// .service(web::resource("/path1").to(|| async { "OK" })) -/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) -/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed))) -/// ); -/// } +/// let app = App::new().service( +/// web::scope("/{project_id}/") +/// .service(web::resource("/path1").to(|| async { "OK" })) +/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) +/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed))) +/// ); /// ``` /// /// In the above example three routes get registered: -/// * /{project_id}/path1 - responds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests +/// - /{project_id}/path1 - responds to all HTTP methods +/// - /{project_id}/path2 - responds to `GET` requests +/// - /{project_id}/path3 - responds to `HEAD` requests +/// +/// [pat]: crate::dev::ResourceDef#prefix-resources +/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments pub struct Scope { endpoint: T, rdef: String, @@ -106,16 +108,14 @@ where /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|r: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// })) - /// ); - /// } + /// let app = App::new().service( + /// web::scope("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|r: HttpRequest| { + /// HttpResponse::MethodNotAllowed() + /// })) + /// ); /// ``` pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(guard)); @@ -186,15 +186,13 @@ where /// ); /// } /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .service( - /// web::scope("/api") - /// .configure(config) - /// ) - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .service( + /// web::scope("/api") + /// .configure(config) + /// ) + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// ``` pub fn configure(mut self, cfg_fn: F) -> Self where @@ -233,13 +231,11 @@ where /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app").service( - /// web::scope("/v1") - /// .service(web::resource("/test1").to(index))) - /// ); - /// } + /// let app = App::new().service( + /// web::scope("/app").service( + /// web::scope("/v1") + /// .service(web::resource("/test1").to(index))) + /// ); /// ``` pub fn service(mut self, factory: F) -> Self where @@ -263,13 +259,11 @@ where /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } + /// let app = App::new().service( + /// web::scope("/app") + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) + /// ); /// ``` pub fn route(self, path: &str, mut route: Route) -> Self { self.service( @@ -355,21 +349,19 @@ where /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route("/index.html", web::get().to(index))); - /// } + /// let app = App::new().service( + /// web::scope("/app") + /// .wrap_fn(|req, srv| { + /// let fut = srv.call(req); + /// async { + /// let mut res = fut.await?; + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// Ok(res) + /// } + /// }) + /// .route("/index.html", web::get().to(index))); /// ``` pub fn wrap_fn( self, diff --git a/src/web.rs b/src/web.rs index 042b8a008..22877692d 100644 --- a/src/web.rs +++ b/src/web.rs @@ -52,11 +52,16 @@ pub fn resource(path: T) -> Resource { /// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic /// path segments. /// +/// # Avoid Trailing Slashes +/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost +/// certainly not have the expected behavior. See the [documentation on resource definitions][pat] +/// to understand why this is the case and how to correctly construct scope/prefix definitions. +/// /// # Examples /// In this example, three routes are set up (and will handle any method): -/// * `/{project_id}/path1` -/// * `/{project_id}/path2` -/// * `/{project_id}/path3` +/// - `/{project_id}/path1` +/// - `/{project_id}/path2` +/// - `/{project_id}/path3` /// /// ``` /// use actix_web::{web, App, HttpResponse}; @@ -68,6 +73,8 @@ pub fn resource(path: T) -> Resource { /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) /// ); /// ``` +/// +/// [pat]: crate::dev::ResourceDef#prefix-resources pub fn scope(path: &str) -> Scope { Scope::new(path) } From 5860fe53814ede54d3f6939af1a457a9b9549613 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 04:43:59 +0000 Subject: [PATCH 220/861] expose Handler trait --- src/handler.rs | 99 ++++++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 1 + src/resource.rs | 6 +-- src/route.rs | 6 +-- src/web.rs | 6 +-- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e543ecc7f..647606890 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -8,26 +8,88 @@ use crate::{ BoxError, FromRequest, HttpResponse, Responder, }; -/// A request handler is an async function that accepts zero or more parameters that can be -/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted -/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait). +/// The interface for request handlers. /// -/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not -/// a valid handler. See for more information. +/// # What Is A Request Handler +/// A request handler has three requirements: +/// 1. It is an async function (or a function/closure that returns an appropriate future); +/// 1. The function accepts zero or more parameters that implement [`FromRequest`]; +/// 1. The async function (or future) resolves to a type that can be converted into an +/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait). /// -/// [`impl FromRequest`]: crate::FromRequest +/// # Compiler Errors +/// If you get the error `the trait Handler<_, _, _> is not implemented`, then your handler does not +/// fulfill one or more of the above requirements. +/// +/// Unfortunately we cannot provide a better compile error message (while keeping the trait's +/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added +/// to Rust. +/// +/// # How Do Handlers Receive Variable Numbers Of Arguments +/// Rest assured there is no macro magic here; it's just traits. +/// +/// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length). +/// +/// Secondly, the `Handler` trait is implemented for functions (up to an [arity] of 12) in a way +/// that aligns their parameter positions with a corresponding tuple of types (becoming the `T` type +/// parameter in this trait's implementation). +/// +/// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the +/// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on +/// that tuple, and the `Handler::call` implementation for that particular function arity +/// destructures the tuple into it's component types and calls your handler function with them. +/// +/// In pseudo-code the process looks something like this: +/// ```ignore +/// async fn my_handler(body: String, state: web::Data) -> impl Responder { +/// ... +/// } +/// +/// // the function params above described as a tuple, names do not matter, only position +/// type InferredMyHandlerArgs = (String, web::Data); +/// +/// // create tuple of arguments to be passed to handler +/// let args = InferredMyHandlerArgs::from_request(&request, &payload).await; +/// +/// // call handler with argument tuple +/// let response = Handler::call(&my_handler, args).await; +/// +/// // which is effectively... +/// +/// let (body, state) = args; +/// let response = my_handler(body, state).await; +/// ``` +/// +/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the +/// bounds of the handler call after argument extraction: +/// ```ignore +/// impl Handler<(Arg1, Arg2), R> for Func +/// where +/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static, +/// R: Future, +/// R::Output: Responder, +/// { +/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R { +/// (self)(arg1, arg2) +/// } +/// } +/// ``` +/// +/// [arity]: https://en.wikipedia.org/wiki/Arity +/// [`from_request`]: FromRequest::from_request +/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628 pub trait Handler: Clone + 'static where R: Future, R::Output: Responder, { - fn call(&self, param: T) -> R; + fn call(&self, args: T) -> R; } -pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory +pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory where - F: Handler, - T: FromRequest, + F: Handler, + Args: FromRequest, R: Future, R::Output: Responder, ::Body: MessageBody, @@ -39,7 +101,7 @@ where async move { let (req, mut payload) = req.into_parts(); - let res = match T::from_request(&req, &mut payload).await { + let res = match Args::from_request(&req, &mut payload).await { Err(err) => HttpResponse::from_error(err), Ok(data) => handler @@ -59,17 +121,18 @@ where /// /// # Examples /// ```ignore -/// factory_tuple! {} // implements Handler for types: fn() -> Res -/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res +/// factory_tuple! {} // implements Handler for types: fn() -> R +/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R /// ``` macro_rules! factory_tuple ({ $($param:ident)* } => { - impl Handler<($($param,)*), Res> for Func - where Func: Fn($($param),*) -> Res + Clone + 'static, - Res: Future, - Res::Output: Responder, + impl Handler<($($param,)*), R> for Func + where Func: Fn($($param),*) -> R + Clone + 'static, + R: Future, + R::Output: Responder, { + #[inline] #[allow(non_snake_case)] - fn call(&self, ($($param,)*): ($($param,)*)) -> Res { + fn call(&self, ($($param,)*): ($($param,)*)) -> R { (self)($($param,)*) } } diff --git a/src/lib.rs b/src/lib.rs index 171a2d101..3462ae90b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,7 @@ pub use cookie; pub use crate::app::App; pub use crate::error::{Error, ResponseError, Result}; pub use crate::extract::FromRequest; +pub use crate::handler::Handler; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder}; diff --git a/src/resource.rs b/src/resource.rs index 0d82bb004..c6c8a5b89 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -232,10 +232,10 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, - I: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody, diff --git a/src/route.rs b/src/route.rs index 16c01275b..6396c4286 100644 --- a/src/route.rs +++ b/src/route.rs @@ -176,10 +176,10 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, - T: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody, diff --git a/src/web.rs b/src/web.rs index 22877692d..46e7704b6 100644 --- a/src/web.rs +++ b/src/web.rs @@ -146,10 +146,10 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Handler, - I: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody + 'static, From 2e493cf7915587f157f6da42b1a0dfaa34d7f097 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 04:53:51 +0000 Subject: [PATCH 221/861] remove crate level clippy allows --- src/app.rs | 42 +++++++++++++-------------- src/app_service.rs | 1 + src/config.rs | 2 ++ src/guard.rs | 58 ++++++++++++++++---------------------- src/handler.rs | 8 +++--- src/lib.rs | 1 - src/middleware/compress.rs | 1 + src/resource.rs | 16 +++++------ src/scope.rs | 1 + src/server.rs | 1 + 10 files changed, 61 insertions(+), 70 deletions(-) diff --git a/src/app.rs b/src/app.rs index b4b952734..10868d18d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -122,9 +122,10 @@ impl App { self.app_data(Data::new(data)) } - /// Add application data factory. This function is similar to `.data()` but it accepts a - /// "data factory". Data values are constructed asynchronously during application - /// initialization, before the server starts accepting requests. + /// Add application data factory that resolves asynchronously. + /// + /// Data items are constructed during application initialization, before the server starts + /// accepting requests. pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, @@ -150,6 +151,7 @@ impl App { } .boxed_local() })); + self } @@ -200,11 +202,9 @@ impl App { /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new() - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); - /// } + /// let app = App::new() + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// ``` pub fn route(self, path: &str, mut route: Route) -> Self { self.service( @@ -243,13 +243,11 @@ impl App { /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/index.html").route(web::get().to(index))) - /// .default_service( - /// web::route().to(|| HttpResponse::NotFound())); - /// } + /// let app = App::new() + /// .service( + /// web::resource("/index.html").route(web::get().to(index))) + /// .default_service( + /// web::route().to(|| HttpResponse::NotFound())); /// ``` /// /// It is also possible to use static files as default service. @@ -257,14 +255,12 @@ impl App { /// ``` /// use actix_web::{web, App, HttpResponse}; /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/index.html").to(|| HttpResponse::Ok())) - /// .default_service( - /// web::to(|| HttpResponse::NotFound()) - /// ); - /// } + /// let app = App::new() + /// .service( + /// web::resource("/index.html").to(|| HttpResponse::Ok())) + /// .default_service( + /// web::to(|| HttpResponse::NotFound()) + /// ); /// ``` pub fn default_service(mut self, svc: F) -> Self where diff --git a/src/app_service.rs b/src/app_service.rs index 4e84cb201..e0d424390 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -236,6 +236,7 @@ where } pub struct AppRoutingFactory { + #[allow(clippy::type_complexity)] services: Rc< [( ResourceDef, diff --git a/src/config.rs b/src/config.rs index 9e77c0f96..cfa9a4ca3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,6 +24,7 @@ pub struct AppService { config: AppConfig, root: bool, default: Rc, + #[allow(clippy::type_complexity)] services: Vec<( ResourceDef, HttpNewService, @@ -48,6 +49,7 @@ impl AppService { self.root } + #[allow(clippy::type_complexity)] pub(crate) fn into_services( self, ) -> ( diff --git a/src/guard.rs b/src/guard.rs index d5c585c1b..db7f06987 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -15,14 +15,12 @@ //! ``` //! use actix_web::{web, http, dev, guard, App, HttpResponse}; //! -//! fn main() { -//! App::new().service(web::resource("/index.html").route( -//! web::route() -//! .guard(guard::Post()) -//! .guard(guard::fn_guard(|head| head.method == http::Method::GET)) -//! .to(|| HttpResponse::MethodNotAllowed())) -//! ); -//! } +//! App::new().service(web::resource("/index.html").route( +//! web::route() +//! .guard(guard::Post()) +//! .guard(guard::fn_guard(|head| head.method == http::Method::GET)) +//! .to(|| HttpResponse::MethodNotAllowed())) +//! ); //! ``` #![allow(non_snake_case)] @@ -53,16 +51,14 @@ impl Guard for Rc { /// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::fn_guard( -/// |req| req.headers() -/// .contains_key("content-type"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard( +/// guard::fn_guard( +/// |req| req.headers() +/// .contains_key("content-type"))) +/// .to(|| HttpResponse::MethodNotAllowed())) +/// ); /// ``` pub fn fn_guard(f: F) -> impl Guard where @@ -96,13 +92,11 @@ where /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard(guard::Any(guard::Get()).or(guard::Post())) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard(guard::Any(guard::Get()).or(guard::Post())) +/// .to(|| HttpResponse::MethodNotAllowed())) +/// ); /// ``` pub fn Any(guard: F) -> AnyGuard { AnyGuard(vec![Box::new(guard)]) @@ -135,14 +129,12 @@ impl Guard for AnyGuard { /// ``` /// use actix_web::{guard, web, App, HttpResponse}; /// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard( +/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) +/// .to(|| HttpResponse::MethodNotAllowed())) +/// ); /// ``` pub fn All(guard: F) -> AllGuard { AllGuard(vec![Box::new(guard)]) diff --git a/src/handler.rs b/src/handler.rs index 647606890..ea6855c7f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -31,8 +31,8 @@ use crate::{ /// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length). /// /// Secondly, the `Handler` trait is implemented for functions (up to an [arity] of 12) in a way -/// that aligns their parameter positions with a corresponding tuple of types (becoming the `T` type -/// parameter in this trait's implementation). +/// that aligns their parameter positions with a corresponding tuple of types (becoming the `Args` +/// type parameter for this trait). /// /// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the /// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on @@ -78,12 +78,12 @@ use crate::{ /// [arity]: https://en.wikipedia.org/wiki/Arity /// [`from_request`]: FromRequest::from_request /// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628 -pub trait Handler: Clone + 'static +pub trait Handler: Clone + 'static where R: Future, R::Output: Responder, { - fn call(&self, args: T) -> R; + fn call(&self, args: Args) -> R; } pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory diff --git a/src/lib.rs b/src/lib.rs index 3462ae90b..5f5b915b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,6 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] -#![allow(clippy::needless_doctest_main, clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index af4a107e3..d3cdf5763 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -113,6 +113,7 @@ where { type Response = ServiceResponse>>; type Error = Error; + #[allow(clippy::type_complexity)] type Future = Either, Ready>>; actix_service::forward_ready!(service); diff --git a/src/resource.rs b/src/resource.rs index c6c8a5b89..f0c6f6d7c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -131,15 +131,13 @@ where /// ``` /// use actix_web::{web, guard, App, HttpResponse}; /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/").route( - /// web::route() - /// .guard(guard::Any(guard::Get()).or(guard::Put())) - /// .guard(guard::Header("Content-Type", "text/plain")) - /// .to(|| HttpResponse::Ok())) - /// ); - /// } + /// let app = App::new().service( + /// web::resource("/").route( + /// web::route() + /// .guard(guard::Any(guard::Get()).or(guard::Put())) + /// .guard(guard::Header("Content-Type", "text/plain")) + /// .to(|| HttpResponse::Ok())) + /// ); /// ``` /// /// Multiple routes could be added to a resource. Resource object uses diff --git a/src/scope.rs b/src/scope.rs index c3bab8f7e..176e0d5a0 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -469,6 +469,7 @@ where } pub struct ScopeFactory { + #[allow(clippy::type_complexity)] services: Rc< [( ResourceDef, diff --git a/src/server.rs b/src/server.rs index b2ff423f1..ed0c965b3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -63,6 +63,7 @@ where backlog: u32, sockets: Vec, builder: ServerBuilder, + #[allow(clippy::type_complexity)] on_connect_fn: Option>, _phantom: PhantomData<(S, B)>, } From ac0c4eb68434a975beacfca20e26cf2596999f5f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 Dec 2021 21:24:03 +0000 Subject: [PATCH 222/861] update actix-tls references to stable 3.0.0 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 4 ++-- awc/Cargo.toml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d15f26172..7b095af91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ actix-rt = "2.3" actix-server = "2.0.0-rc.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } +actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = "3.0.0-beta.16" actix-router = "0.5.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e1c875a1f..2ad620b08 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -31,7 +31,7 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" actix-codec = "0.4.1" -actix-tls = "3.0.0-rc.1" +actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c15f5ee28..3fa437562 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -71,7 +71,7 @@ sha-1 = "0.10" smallvec = "1.6.1" # tls -actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } +actix-tls = { version = "3.0.0", default-features = false, optional = true } # compression brotli2 = { version="0.3.2", optional = true } @@ -81,7 +81,7 @@ zstd = { version = "0.9", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" -actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] } +actix-tls = { version = "3.0.0", features = ["openssl"] } actix-web = "4.0.0-beta.15" async-stream = "0.3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 4b29aac16..d0494e023 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -62,7 +62,7 @@ actix-codec = "0.4.1" actix-service = "2.0.0" actix-http = "3.0.0-beta.16" actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.0.0-rc.2", features = ["connect", "uri"] } +actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" ahash = "0.7" @@ -97,7 +97,7 @@ actix-http = { version = "3.0.0-beta.16", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-server = "2.0.0-rc.1" actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } -actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] } +actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.15", features = ["openssl"] } From 554ae7a868b169b9210c9c24002ca1afd96f0c71 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 27 Dec 2021 03:44:30 +0300 Subject: [PATCH 223/861] rework Handler trait (#2549) --- src/handler.rs | 37 +++++++++++++++++-------------------- src/resource.rs | 11 ++++------- src/route.rs | 14 +++++--------- src/web.rs | 15 ++++++--------- 4 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index ea6855c7f..d458e22e1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -3,9 +3,8 @@ use std::future::Future; use actix_service::{boxed, fn_service}; use crate::{ - body::MessageBody, service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, - BoxError, FromRequest, HttpResponse, Responder, + FromRequest, HttpResponse, Responder, }; /// The interface for request handlers. @@ -18,7 +17,7 @@ use crate::{ /// [`HttpResponse`] (i.e., it implements the [`Responder`] trait). /// /// # Compiler Errors -/// If you get the error `the trait Handler<_, _, _> is not implemented`, then your handler does not +/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not /// fulfill one or more of the above requirements. /// /// Unfortunately we cannot provide a better compile error message (while keeping the trait's @@ -78,22 +77,18 @@ use crate::{ /// [arity]: https://en.wikipedia.org/wiki/Arity /// [`from_request`]: FromRequest::from_request /// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628 -pub trait Handler: Clone + 'static -where - R: Future, - R::Output: Responder, -{ - fn call(&self, args: Args) -> R; +pub trait Handler: Clone + 'static { + type Output; + type Future: Future; + + fn call(&self, args: Args) -> Self::Future; } -pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory +pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory where - F: Handler, + F: Handler, Args: FromRequest, - R: Future, - R::Output: Responder, - ::Body: MessageBody, - <::Body as MessageBody>::Error: Into, + F::Output: Responder, { boxed::factory(fn_service(move |req: ServiceRequest| { let handler = handler.clone(); @@ -125,14 +120,16 @@ where /// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R /// ``` macro_rules! factory_tuple ({ $($param:ident)* } => { - impl Handler<($($param,)*), R> for Func - where Func: Fn($($param),*) -> R + Clone + 'static, - R: Future, - R::Output: Responder, + impl Handler<($($param,)*)> for Func + where Func: Fn($($param),*) -> Fut + Clone + 'static, + Fut: Future, { + type Output = Fut::Output; + type Future = Fut; + #[inline] #[allow(non_snake_case)] - fn call(&self, ($($param,)*): ($($param,)*)) -> R { + fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future { (self)($($param,)*) } } diff --git a/src/resource.rs b/src/resource.rs index f0c6f6d7c..564c4d3ef 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -20,7 +20,7 @@ use crate::{ BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, ServiceResponse, }, - BoxError, Error, FromRequest, HttpResponse, Responder, + Error, FromRequest, HttpResponse, Responder, }; /// A collection of [`Route`]s that respond to the same path pattern. @@ -230,14 +230,11 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, + F: Handler, Args: FromRequest + 'static, - R: Future + 'static, - R::Output: Responder + 'static, - ::Body: MessageBody, - <::Body as MessageBody>::Error: Into, + F::Output: Responder + 'static, { self.routes.push(Route::new().to(handler)); self diff --git a/src/route.rs b/src/route.rs index 6396c4286..6d6fca4b7 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,4 @@ -use std::{future::Future, mem, rc::Rc}; +use std::{mem, rc::Rc}; use actix_http::Method; use actix_service::{ @@ -8,11 +8,10 @@ use actix_service::{ use futures_core::future::LocalBoxFuture; use crate::{ - body::MessageBody, guard::{self, Guard}, handler::{handler_service, Handler}, service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, - BoxError, Error, FromRequest, HttpResponse, Responder, + Error, FromRequest, HttpResponse, Responder, }; /// A request handler with [guards](guard). @@ -176,14 +175,11 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, + F: Handler, Args: FromRequest + 'static, - R: Future + 'static, - R::Output: Responder + 'static, - ::Body: MessageBody, - <::Body as MessageBody>::Error: Into, + F::Output: Responder + 'static, { self.service = handler_service(handler); self diff --git a/src/web.rs b/src/web.rs index 46e7704b6..47bff36a3 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,14 +1,14 @@ //! Essentials helper functions and types for application registration. -use std::{error::Error as StdError, future::Future}; +use std::future::Future; use actix_http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::{ - body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler, - resource::Resource, route::Route, scope::Scope, service::WebService, Responder, + error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, + route::Route, scope::Scope, service::WebService, Responder, }; pub use crate::config::ServiceConfig; @@ -146,14 +146,11 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Handler, + F: Handler, Args: FromRequest + 'static, - R: Future + 'static, - R::Output: Responder + 'static, - ::Body: MessageBody + 'static, - <::Body as MessageBody>::Error: Into>, + F::Output: Responder + 'static, { Route::new().to(handler) } From 2308f8afa4b71dad726d5a3534e7eb4b1469098a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 27 Dec 2021 16:15:20 +0000 Subject: [PATCH 224/861] use const header values where possible --- actix-http/src/h2/dispatcher.rs | 8 +++++--- actix-http/src/header/shared/content_encoding.rs | 4 ++-- actix-http/src/ws/mod.rs | 5 +++-- awc/src/client/h2proto.rs | 8 +++++--- awc/src/ws.rs | 13 ++++++++----- src/middleware/default_headers.rs | 7 +++---- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7f0f15ee6..a90eb3466 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -288,9 +288,11 @@ fn prepare_response( let _ = match size { BodySize::None | BodySize::Stream => None, - BodySize::Sized(0) => res - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(0) => { + #[allow(clippy::declare_interior_mutable_const)] + const HV_ZERO: HeaderValue = HeaderValue::from_static("0"); + res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO) + } BodySize::Sized(len) => { let mut buf = itoa::Buffer::new(); diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index a6e52138d..68511a8ee 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -45,13 +45,13 @@ pub enum ContentEncoding { impl ContentEncoding { /// Is the content compressed? #[inline] - pub fn is_compression(self) -> bool { + pub const fn is_compression(self) -> bool { matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) } /// Convert content encoding to string. #[inline] - pub fn as_str(self) -> &'static str { + pub const fn as_str(self) -> &'static str { match self { ContentEncoding::Br => "br", ContentEncoding::Gzip => "gzip", diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 6491da149..568d801a2 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -99,8 +99,9 @@ impl From for Response { match err { HandshakeError::GetMethodRequired => { let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED); - res.headers_mut() - .insert(header::ALLOW, HeaderValue::from_static("GET")); + #[allow(clippy::declare_interior_mutable_const)] + const HV_GET: HeaderValue = HeaderValue::from_static("GET"); + res.headers_mut().insert(header::ALLOW, HV_GET); res } diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs index 9ced5776b..709896ddd 100644 --- a/awc/src/client/h2proto.rs +++ b/awc/src/client/h2proto.rs @@ -52,9 +52,11 @@ where let _ = match length { BodySize::None => None, - BodySize::Sized(0) => req - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(0) => { + #[allow(clippy::declare_interior_mutable_const)] + const HV_ZERO: HeaderValue = HeaderValue::from_static("0"); + req.headers_mut().insert(CONTENT_LENGTH, HV_ZERO) + } BodySize::Sized(len) => { let mut buf = itoa::Buffer::new(); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 96f8cf893..f3ee02d43 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -300,13 +300,16 @@ impl WebsocketsRequest { } self.head.set_connection_type(ConnectionType::Upgrade); + + #[allow(clippy::declare_interior_mutable_const)] + const HV_WEBSOCKET: HeaderValue = HeaderValue::from_static("websocket"); + self.head.headers.insert(header::UPGRADE, HV_WEBSOCKET); + + #[allow(clippy::declare_interior_mutable_const)] + const HV_THIRTEEN: HeaderValue = HeaderValue::from_static("13"); self.head .headers - .insert(header::UPGRADE, HeaderValue::from_static("websocket")); - self.head.headers.insert( - header::SEC_WEBSOCKET_VERSION, - HeaderValue::from_static("13"), - ); + .insert(header::SEC_WEBSOCKET_VERSION, HV_THIRTEEN); if let Some(protocols) = self.protocols.take() { self.head.headers.insert( diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 89210b156..003abd40d 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -100,10 +100,9 @@ impl DefaultHeaders { /// /// Default is `application/octet-stream`. pub fn add_content_type(self) -> Self { - self.add(( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - )) + #[allow(clippy::declare_interior_mutable_const)] + const HV_MIME: HeaderValue = HeaderValue::from_static("application/octet-stream"); + self.add((CONTENT_TYPE, HV_MIME)) } } From 76684a786ed82a4e39acb81eedf798de942e4b61 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 27 Dec 2021 18:45:31 +0000 Subject: [PATCH 225/861] update server dep to rc2 (#2550) --- CHANGES.md | 3 +++ Cargo.toml | 10 +++++----- README.md | 4 ++-- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 10 +++++----- actix-files/README.md | 4 ++-- actix-http-test/CHANGES.md | 6 ++++++ actix-http-test/Cargo.toml | 10 +++++----- actix-http-test/README.md | 4 ++-- actix-http-test/src/lib.rs | 6 +++--- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 8 ++++---- actix-http/README.md | 4 ++-- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 6 +++--- actix-multipart/README.md | 4 ++-- actix-test/CHANGES.md | 4 ++++ actix-test/Cargo.toml | 10 +++++----- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 10 +++++----- actix-web-actors/README.md | 4 ++-- actix-web-codegen/Cargo.toml | 4 ++-- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 14 +++++++------- awc/README.md | 4 ++-- 25 files changed, 89 insertions(+), 58 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a43b3ee41..e1317a7a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.16 - 2021-12-27 ### Changed - No longer require `Scope` service body type to be boxed. [#2523] - No longer require `Resource` service body type to be boxed. [#2526] diff --git a/Cargo.toml b/Cargo.toml index 7b095af91..b6ef184e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.15" +version = "4.0.0-beta.16" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -72,12 +72,12 @@ experimental-io-uring = ["actix-server/io-uring"] actix-codec = "0.4.1" actix-macros = "0.2.3" actix-rt = "2.3" -actix-server = "2.0.0-rc.1" +actix-server = "2.0.0-rc.2" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = "3.0.0-beta.16" +actix-http = "3.0.0-beta.17" actix-router = "0.5.0-beta.3" actix-web-codegen = "0.5.0-beta.6" @@ -106,8 +106,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.14", features = ["openssl"] } +actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } +awc = { version = "3.0.0-beta.15", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/README.md b/README.md index a9afbf386..f9d388f8b 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.15)](https://docs.rs/actix-web/4.0.0-beta.15) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.16)](https://docs.rs/actix-web/4.0.0-beta.16) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.15) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.16)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ef8eba0fc..af6dcb415 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.11 - 2021-12-27 +* No significant changes since `0.6.0-beta.10`. + + ## 0.6.0-beta.10 - 2021-12-11 - No significant changes since `0.6.0-beta.9`. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c74a8e9a6..bbd4fee22 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.10" +version = "0.6.0-beta.11" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -22,10 +22,10 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.16" +actix-http = "3.0.0-beta.17" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.15", default-features = false } +actix-web = { version = "4.0.0-beta.16", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.9" -actix-web = "4.0.0-beta.15" +actix-test = "0.1.0-beta.10" +actix-web = "4.0.0-beta.16" diff --git a/actix-files/README.md b/actix-files/README.md index d686e255c..db5c94d1e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.11)](https://docs.rs/actix-files/0.6.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.10/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.10) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.11/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.11) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 4e86e20e8..8c6a63b72 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.10 - 2021-12-27 +- Update `actix-server` to `2.0.0-rc.2`. [#2550] + +[#2550]: https://github.com/actix/actix-web/pull/2550 + + ## 3.0.0-beta.9 - 2021-12-11 - No significant changes since `3.0.0-beta.8`. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2ad620b08..f071678a4 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.9" +version = "3.0.0-beta.10" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] @@ -34,8 +34,8 @@ actix-codec = "0.4.1" actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-server = "2.0.0-rc.1" -awc = { version = "3.0.0-beta.14", default-features = false } +actix-server = "2.0.0-rc.2" +awc = { version = "3.0.0-beta.15", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.16" +actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.17" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index a0a8f1cd8..589c54c23 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http-test/3.0.0-beta.9) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http-test/3.0.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.9) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.10) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 03239ece6..8636ef9c4 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -12,7 +12,7 @@ use std::{net, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; -use actix_server::{Server, ServiceFactory}; +use actix_server::{Server, ServerServiceFactory}; use awc::{ error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector, @@ -51,13 +51,13 @@ use tokio::sync::mpsc; /// assert!(response.status().is_success()); /// } /// ``` -pub async fn test_server>(factory: F) -> TestServer { +pub async fn test_server>(factory: F) -> TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); test_server_with_addr(tcp, factory).await } /// Start [`test server`](test_server()) on an existing address binding. -pub async fn test_server_with_addr>( +pub async fn test_server_with_addr>( tcp: net::TcpListener, factory: F, ) -> TestServer { diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index adc4c35c7..d74a754ac 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.17 - 2021-12-27 ### Changes - `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] - `Payload` inner fields are now named. [#2545] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3fa437562..b27cb4053 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.16" +version = "3.0.0-beta.17" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] @@ -79,10 +79,10 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } -actix-server = "2.0.0-rc.1" +actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.15" +actix-web = "4.0.0-beta.16" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http/README.md b/actix-http/README.md index 05edffd2c..223e18ceb 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.16)](https://docs.rs/actix-http/3.0.0-beta.16) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.17)](https://docs.rs/actix-http/3.0.0-beta.17) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.16) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.17) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index e58c3ee24..a9a1e8784 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.11 - 2021-12-27 +* No significant changes since `0.4.0-beta.10`. + + ## 0.4.0-beta.10 - 2021-12-11 - No significant changes since `0.4.0-beta.9`. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 595c14d7e..8338eb952 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.10" +version = "0.4.0-beta.11" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.15", default-features = false } +actix-web = { version = "4.0.0-beta.16", default-features = false } bytes = "1" derive_more = "0.99.5" @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.16" +actix-http = "3.0.0-beta.17" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 647796557..a9ee325ba 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.10)](https://docs.rs/actix-multipart/0.4.0-beta.10) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.11)](https://docs.rs/actix-multipart/0.4.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.10/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.10) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.11/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.11) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index e3deeb3f4..2de0a69d6 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.10 - 2021-12-27 +* No significant changes since `0.1.0-beta.9`. + + ## 0.1.0-beta.9 - 2021-12-17 - Re-export `actix_http::body::to_bytes`. [#2518] - Update `actix_web::test` re-exports. [#2518] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 4a4615820..b34d8e6ee 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.9" +version = "0.1.0-beta.10" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.16" -actix-http-test = "3.0.0-beta.9" +actix-http = "3.0.0-beta.17" +actix-http-test = "3.0.0-beta.10" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.15", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 6abfe2c61..2fbbe7444 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.9 - 2021-12-27 +* No significant changes since `4.0.0-beta.8`. + + ## 4.0.0-beta.8 - 2021-12-11 - Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] - Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index d57f139f6..9dba2dfbd 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.8" +version = "4.0.0-beta.9" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] @@ -16,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.16" -actix-web = { version = "4.0.0-beta.15", default-features = false } +actix-http = "3.0.0-beta.17" +actix-web = { version = "4.0.0-beta.16", default-features = false } bytes = "1" bytestring = "1" @@ -27,8 +27,8 @@ tokio = { version = "1.8", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.9" -awc = { version = "3.0.0-beta.14", default-features = false } +actix-test = "0.1.0-beta.10" +awc = { version = "3.0.0-beta.15", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 954f8273b..232c81eac 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web-actors/4.0.0-beta.8) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web-actors/4.0.0-beta.9) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 8d42137e7..5250baa90 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,9 +23,9 @@ actix-router = "0.5.0-beta.3" [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.9" +actix-test = "0.1.0-beta.10" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.15" +actix-web = "4.0.0-beta.16" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e1a059481..200ad846a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.15 - 2021-12-27 - Rename `Connector::{ssl => openssl}`. [#2503] - Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] - `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d0494e023..b77662de0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.14" +version = "3.0.0-beta.15" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.16" +actix-http = "3.0.0-beta.17" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.16", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } -actix-server = "2.0.0-rc.1" -actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } +actix-http = { version = "3.0.0-beta.17", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-server = "2.0.0-rc.2" +actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.15", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.16", features = ["openssl"] } brotli2 = "0.3.2" env_logger = "0.9" diff --git a/awc/README.md b/awc/README.md index 3fbdd903a..582ecb18f 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.14)](https://docs.rs/awc/3.0.0-beta.14) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.15)](https://docs.rs/awc/3.0.0-beta.15) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.14/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.14) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.15/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.15) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 36193b0a50d7453fb55d0c4d72b3409fa767426e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 27 Dec 2021 18:54:10 +0000 Subject: [PATCH 226/861] specify tokio dep to avoid RUSTSEC-2021-0124 warning --- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index f071678a4..5c58978ea 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -48,7 +48,7 @@ serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } -tokio = { version = "1.8", features = ["sync"] } +tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b27cb4053..9e587890b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -96,7 +96,7 @@ serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -tokio = { version = "1.8", features = ["net", "rt", "macros"] } +tokio = { version = "1.8.4", features = ["net", "rt", "macros"] } [[example]] name = "ws" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 8338eb952..de13133a1 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -30,5 +30,5 @@ twoway = "0.2" actix-rt = "2.2" actix-http = "3.0.0-beta.17" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } -tokio = { version = "1.8", features = ["sync"] } +tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b34d8e6ee..c7177a38c 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -45,4 +45,4 @@ serde_json = "1" serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true } -tokio = { version = "1.8", features = ["sync"] } +tokio = { version = "1.8.4", features = ["sync"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 9dba2dfbd..3b792093a 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -23,7 +23,7 @@ bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" -tokio = { version = "1.8", features = ["sync"] } +tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b77662de0..8777ffa74 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -83,7 +83,7 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -tokio = { version = "1.8", features = ["sync"] } +tokio = { version = "1.8.4", features = ["sync"] } cookie = { version = "0.15", features = ["percent-encode"], optional = true } From 4616ca8ee65ca1e14e784b8b46dc1a1306b422a5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 28 Dec 2021 02:37:13 +0000 Subject: [PATCH 227/861] rework `Guard` trait (#2552) --- CHANGES.md | 11 + actix-files/src/service.rs | 8 +- actix-http/src/message.rs | 6 +- src/app_service.rs | 13 +- src/dev.rs | 23 +- src/guard.rs | 742 +++++++++++++++++++++--------------- src/middleware/normalize.rs | 12 +- src/route.rs | 38 +- src/scope.rs | 7 +- src/service.rs | 34 +- src/test/test_request.rs | 2 +- 11 files changed, 545 insertions(+), 351 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e1317a7a5..f925f3b94 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- `guard::GuardContext` for use with the `Guard` trait. [#2552] +- `ServiceRequest::guard_ctx` for obtaining a guard context. [#2552] + +### Changed +- `Guard` trait now receives a `&GuardContext`. [#2552] +- `guard::fn_guard` functions now receives a `&GuardContext`. [#2552] +- Some guards now return `impl Guard` and their concrete types are made private: `guard::{Header}` and all the method guards. [#2552] +- The `Not` guard is now generic over the type of guard it wraps. [#2552] + +[#2552]: https://github.com/actix/actix-web/pull/2552 ## 4.0.0-beta.16 - 2021-12-27 diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index f6e1c2e11..057dbe5a3 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,8 +1,8 @@ use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc}; -use actix_service::Service; use actix_web::{ - dev::{ServiceRequest, ServiceResponse}, + body::BoxBody, + dev::{Service, ServiceRequest, ServiceResponse}, error::Error, guard::Guard, http::{header, Method}, @@ -94,7 +94,7 @@ impl fmt::Debug for FilesService { } impl Service for FilesService { - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -103,7 +103,7 @@ impl Service for FilesService { 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()) + (**guard).check(&req.guard_ctx()) } else { // default behavior matches!(*req.method(), Method::HEAD | Method::GET) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 34213f68a..ecd08fbb3 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, ops, rc::Rc}; use bitflags::bitflags; @@ -49,7 +49,7 @@ impl Message { } } -impl std::ops::Deref for Message { +impl ops::Deref for Message { type Target = T; fn deref(&self) -> &Self::Target { @@ -57,7 +57,7 @@ impl std::ops::Deref for Message { } } -impl std::ops::DerefMut for Message { +impl ops::DerefMut for Message { fn deref_mut(&mut self) -> &mut Self::Target { Rc::get_mut(&mut self.head).expect("Multiple copies exist") } diff --git a/src/app_service.rs b/src/app_service.rs index e0d424390..56b24f0d8 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,14 +1,16 @@ use std::{cell::RefCell, mem, rc::Rc}; -use actix_http::{Extensions, Request}; +use actix_http::Request; use actix_router::{Path, ResourceDef, Router, Url}; use actix_service::{boxed, fn_service, Service, ServiceFactory}; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; use crate::{ + body::BoxBody, config::{AppConfig, AppService}, data::FnDataFactory, + dev::Extensions, guard::Guard, request::{HttpRequest, HttpRequestPool}, rmap::ResourceMap, @@ -297,7 +299,7 @@ pub struct AppRouting { } impl Service for AppRouting { - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -306,12 +308,15 @@ impl Service for AppRouting { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { if let Some(ref guards) = guards { - for f in guards { - if !f.check(req.head()) { + let guard_ctx = req.guard_ctx(); + + for guard in guards { + if !guard.check(&guard_ctx) { return false; } } } + true }); diff --git a/src/dev.rs b/src/dev.rs index 6e1970467..bb1385bde 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -3,6 +3,16 @@ //! Most users will not have to interact with the types in this module, but it is useful for those //! writing extractors, middleware, libraries, or interacting with the service API directly. +pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead}; +pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; +pub use actix_server::{Server, ServerHandle}; +pub use actix_service::{ + always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, +}; + +#[cfg(feature = "__compress")] +pub use actix_http::encoding::Decoder as Decompress; + pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] pub use crate::handler::Handler; @@ -14,16 +24,6 @@ pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; -pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead}; -pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; -pub use actix_server::{Server, ServerHandle}; -pub use actix_service::{ - always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, -}; - -#[cfg(feature = "__compress")] -pub use actix_http::encoding::Decoder as Decompress; - use crate::http::header::ContentEncoding; use actix_router::Patterns; @@ -46,7 +46,6 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { patterns } -struct Enc(ContentEncoding); /// Helper trait that allows to set specific encoding for response. pub trait BodyEncoding { @@ -70,6 +69,8 @@ impl BodyEncoding for actix_http::ResponseBuilder { } } +struct Enc(ContentEncoding); + impl BodyEncoding for actix_http::Response { fn get_encoding(&self) -> Option { self.extensions().get::().map(|enc| enc.0) diff --git a/src/guard.rs b/src/guard.rs index db7f06987..fb3e4f243 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -1,160 +1,248 @@ -//! Route match guards. +//! Route guards. //! -//! Guards are one of the ways how actix-web router chooses a -//! handler service. In essence it is just a function that accepts a -//! reference to a `RequestHead` instance and returns a boolean. -//! It is possible to add guards to *scopes*, *resources* -//! and *routes*. Actix provide several guards by default, like various -//! http methods, header, etc. To become a guard, type must implement `Guard` -//! trait. Simple functions could be guards as well. +//! Guards are used during routing to help select a matching service or handler using some aspect of +//! the request; though guards should not be used for path matching since it is a built-in function +//! of the Actix Web router. //! -//! Guards can not modify the request object. But it is possible -//! to store extra attributes on a request by using the `Extensions` container. -//! Extensions containers are available via the `RequestHead::extensions()` method. +//! Guards can be used on [`Scope`]s, [`Resource`]s, [`Route`]s, and other custom services. //! +//! Fundamentally, a guard is a predicate function that receives a reference to a request context +//! object and returns a boolean; true if the request _should_ be handled by the guarded service +//! or handler. This interface is defined by the [`Guard`] trait. +//! +//! Commonly-used guards are provided in this module as well as way of creating a guard from a +//! closure ([`fn_guard`]). The [`Not`], [`Any`], and [`All`] guards are noteworthy, as they can be +//! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on +//! services multiple times (which might have different combining behavior than you want). +//! +//! Guards can not modify anything about the request. However, it is possible to store extra +//! attributes in the request-local data container obtained with [`GuardContext::req_data_mut`]. +//! +//! Guards can prevent resource definitions from overlapping (resulting in some inaccessible routes) +//! where they otherwise would when only considering paths. See the virtual hosting example below. +//! +//! # Examples +//! In the following code, the `/guarded` resource has one defined route whose handler will only be +//! called if the request method is `POST` and there is a request header with name and value equal +//! to `x-guarded` and `secret`, respectively. //! ``` -//! use actix_web::{web, http, dev, guard, App, HttpResponse}; +//! use actix_web::{web, http::Method, guard, HttpResponse}; //! -//! App::new().service(web::resource("/index.html").route( +//! web::resource("/guarded").route( //! web::route() -//! .guard(guard::Post()) -//! .guard(guard::fn_guard(|head| head.method == http::Method::GET)) -//! .to(|| HttpResponse::MethodNotAllowed())) +//! .guard(guard::Any(guard::Get()).or(guard::Post())) +//! .guard(guard::Header("x-guarded", "secret")) +//! .to(|| HttpResponse::Ok()) //! ); //! ``` +//! +//! Guards can be used to set up some form of [virtual hosting] within a single app. +//! Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard +//! definitions they become safe to use in this way. Without these host guards, only routes under +//! the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1` +//! and `localhost` as the `Host` guards. +//! ``` +//! use actix_web::{web, http::Method, guard, App, HttpResponse}; +//! +//! App::new() +//! .service( +//! web::scope("") +//! .guard(guard::Host("www.rust-lang.org")) +//! .default_service(web::to(|| HttpResponse::Ok().body("marketing site"))), +//! ) +//! .service( +//! web::scope("") +//! .guard(guard::Host("play.rust-lang.org")) +//! .default_service(web::to(|| HttpResponse::Ok().body("playground frontend"))), +//! ); +//! ``` +//! +//! [`Scope`]: crate::Scope::guard() +//! [`Resource`]: crate::Resource::guard() +//! [`Route`]: crate::Route::guard() +//! [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting -#![allow(non_snake_case)] +use std::{ + cell::{Ref, RefMut}, + convert::TryFrom, + rc::Rc, +}; -use std::rc::Rc; -use std::{convert::TryFrom, ops::Deref}; +use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead}; -use actix_http::{header, uri::Uri, Method as HttpMethod, RequestHead}; +use crate::service::ServiceRequest; -/// Trait defines resource guards. Guards are used for route selection. -/// -/// Guards can not modify the request object. But it is possible -/// to store extra attributes on a request by using the `Extensions` container. -/// Extensions containers are available via the `RequestHead::extensions()` method. -pub trait Guard { - /// Check if request matches predicate - fn check(&self, request: &RequestHead) -> bool; +/// Provides access to request parts that are useful during routing. +#[derive(Debug)] +pub struct GuardContext<'a> { + pub(crate) req: &'a ServiceRequest, } -impl Guard for Rc { - fn check(&self, request: &RequestHead) -> bool { - self.deref().check(request) +impl<'a> GuardContext<'a> { + /// Returns reference to the request head. + #[inline] + pub fn head(&self) -> &RequestHead { + self.req.head() + } + + /// Returns reference to the request-local data container. + #[inline] + pub fn req_data(&self) -> Ref<'a, Extensions> { + self.req.req_data() + } + + /// Returns mutable reference to the request-local data container. + #[inline] + pub fn req_data_mut(&self) -> RefMut<'a, Extensions> { + self.req.req_data_mut() } } -/// Create guard object for supplied function. +/// Interface for routing guards. /// +/// See [module level documentation](self) for more. +pub trait Guard { + /// Returns true if predicate condition is met for a given request. + fn check(&self, ctx: &GuardContext<'_>) -> bool; +} + +impl Guard for Rc { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + (**self).check(ctx) + } +} + +/// Creates a guard using the given function. +/// +/// # Examples /// ``` -/// use actix_web::{guard, web, App, HttpResponse}; +/// use actix_web::{guard, web, HttpResponse}; /// -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::fn_guard( -/// |req| req.headers() -/// .contains_key("content-type"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); +/// web::route() +/// .guard(guard::fn_guard(|ctx| { +/// ctx.head().headers().contains_key("content-type") +/// })) +/// .to(|| HttpResponse::Ok()); /// ``` pub fn fn_guard(f: F) -> impl Guard where - F: Fn(&RequestHead) -> bool, + F: Fn(&GuardContext<'_>) -> bool, { FnGuard(f) } -struct FnGuard bool>(F); +struct FnGuard) -> bool>(F); impl Guard for FnGuard where - F: Fn(&RequestHead) -> bool, + F: Fn(&GuardContext<'_>) -> bool, { - fn check(&self, head: &RequestHead) -> bool { - (self.0)(head) + fn check(&self, ctx: &GuardContext<'_>) -> bool { + (self.0)(ctx) } } impl Guard for F where - F: Fn(&RequestHead) -> bool, + F: Fn(&GuardContext<'_>) -> bool, { - fn check(&self, head: &RequestHead) -> bool { - (self)(head) + fn check(&self, ctx: &GuardContext<'_>) -> bool { + (self)(ctx) } } -/// Return guard that matches if any of supplied guards. +/// Creates a guard that matches if any added guards match. /// +/// # Examples +/// The handler below will be called for either request method `GET` or `POST`. /// ``` -/// use actix_web::{web, guard, App, HttpResponse}; +/// use actix_web::{web, guard, HttpResponse}; /// -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard(guard::Any(guard::Get()).or(guard::Post())) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); +/// web::route() +/// .guard( +/// guard::Any(guard::Get()) +/// .or(guard::Post())) +/// .to(|| HttpResponse::Ok()); /// ``` +#[allow(non_snake_case)] pub fn Any(guard: F) -> AnyGuard { - AnyGuard(vec![Box::new(guard)]) + AnyGuard { + guards: vec![Box::new(guard)], + } } -/// Matches any of supplied guards. -pub struct AnyGuard(Vec>); +/// A collection of guards that match if the disjunction of their `check` outcomes is true. +/// +/// That is, only one contained guard needs to match in order for the aggregate guard to match. +/// +/// Construct an `AnyGuard` using [`Any`]. +pub struct AnyGuard { + guards: Vec>, +} impl AnyGuard { - /// Add guard to a list of guards to check + /// Adds new guard to the collection of guards to check. pub fn or(mut self, guard: F) -> Self { - self.0.push(Box::new(guard)); + self.guards.push(Box::new(guard)); self } } impl Guard for AnyGuard { - fn check(&self, req: &RequestHead) -> bool { - for p in &self.0 { - if p.check(req) { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + for guard in &self.guards { + if guard.check(ctx) { return true; } } + false } } -/// Return guard that matches if all of the supplied guards. +/// Creates a guard that matches if all added guards match. /// +/// # Examples +/// The handler below will only be called if the request method is `GET` **and** the specified +/// header name and value match exactly. /// ``` -/// use actix_web::{guard, web, App, HttpResponse}; +/// use actix_web::{guard, web, HttpResponse}; /// -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); +/// web::route() +/// .guard( +/// guard::All(guard::Get()) +/// .and(guard::Header("accept", "text/plain")) +/// ) +/// .to(|| HttpResponse::Ok()); /// ``` +#[allow(non_snake_case)] pub fn All(guard: F) -> AllGuard { - AllGuard(vec![Box::new(guard)]) + AllGuard { + guards: vec![Box::new(guard)], + } } -/// Matches if all of supplied guards. -pub struct AllGuard(Vec>); +/// A collection of guards that match if the conjunction of their `check` outcomes is true. +/// +/// That is, **all** contained guard needs to match in order for the aggregate guard to match. +/// +/// Construct an `AllGuard` using [`All`]. +pub struct AllGuard { + guards: Vec>, +} impl AllGuard { - /// Add new guard to the list of guards to check + /// Adds new guard to the collection of guards to check. pub fn and(mut self, guard: F) -> Self { - self.0.push(Box::new(guard)); + self.guards.push(Box::new(guard)); self } } impl Guard for AllGuard { - fn check(&self, request: &RequestHead) -> bool { - for p in &self.0 { - if !p.check(request) { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + for guard in &self.guards { + if !guard.check(ctx) { return false; } } @@ -162,159 +250,189 @@ impl Guard for AllGuard { } } -/// Return guard that matches if supplied guard does not match. -pub fn Not(guard: F) -> NotGuard { - NotGuard(Box::new(guard)) -} +/// Wraps a guard and inverts the outcome of it's `Guard` implementation. +/// +/// # Examples +/// The handler below will be called for any request method apart from `GET`. +/// ``` +/// use actix_web::{guard, web, HttpResponse}; +/// +/// web::route() +/// .guard(guard::Not(guard::Get())) +/// .to(|| HttpResponse::Ok()); +/// ``` +pub struct Not(pub G); -#[doc(hidden)] -pub struct NotGuard(Box); - -impl Guard for NotGuard { - fn check(&self, request: &RequestHead) -> bool { - !self.0.check(request) +impl Guard for Not { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + !self.0.check(ctx) } } -/// HTTP method guard. -#[doc(hidden)] -pub struct MethodGuard(HttpMethod); - -impl Guard for MethodGuard { - fn check(&self, request: &RequestHead) -> bool { - request.method == self.0 - } -} - -/// Guard to match *GET* HTTP method. -pub fn Get() -> MethodGuard { - MethodGuard(HttpMethod::GET) -} - -/// Predicate to match *POST* HTTP method. -pub fn Post() -> MethodGuard { - MethodGuard(HttpMethod::POST) -} - -/// Predicate to match *PUT* HTTP method. -pub fn Put() -> MethodGuard { - MethodGuard(HttpMethod::PUT) -} - -/// Predicate to match *DELETE* HTTP method. -pub fn Delete() -> MethodGuard { - MethodGuard(HttpMethod::DELETE) -} - -/// Predicate to match *HEAD* HTTP method. -pub fn Head() -> MethodGuard { - MethodGuard(HttpMethod::HEAD) -} - -/// Predicate to match *OPTIONS* HTTP method. -pub fn Options() -> MethodGuard { - MethodGuard(HttpMethod::OPTIONS) -} - -/// Predicate to match *CONNECT* HTTP method. -pub fn Connect() -> MethodGuard { - MethodGuard(HttpMethod::CONNECT) -} - -/// Predicate to match *PATCH* HTTP method. -pub fn Patch() -> MethodGuard { - MethodGuard(HttpMethod::PATCH) -} - -/// Predicate to match *TRACE* HTTP method. -pub fn Trace() -> MethodGuard { - MethodGuard(HttpMethod::TRACE) -} - -/// Predicate to match specified HTTP method. -pub fn Method(method: HttpMethod) -> MethodGuard { +/// Creates a guard that matches a specified HTTP method. +#[allow(non_snake_case)] +pub fn Method(method: HttpMethod) -> impl Guard { MethodGuard(method) } -/// Return predicate that matches if request contains specified header and -/// value. -pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard { +/// HTTP method guard. +struct MethodGuard(HttpMethod); + +impl Guard for MethodGuard { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + ctx.head().method == self.0 + } +} + +macro_rules! method_guard { + ($method_fn:ident, $method_const:ident) => { + paste::paste! { + #[doc = " Creates a guard that matches the `" $method_const "` request method."] + /// + /// # Examples + #[doc = " The route in this example will only respond to `" $method_const "` requests."] + /// ``` + /// use actix_web::{guard, web, HttpResponse}; + /// + /// web::route() + #[doc = " .guard(guard::" $method_fn "())"] + /// .to(|| HttpResponse::Ok()); + /// ``` + #[allow(non_snake_case)] + pub fn $method_fn() -> impl Guard { + MethodGuard(HttpMethod::$method_const) + } + } + }; +} + +method_guard!(Get, GET); +method_guard!(Post, POST); +method_guard!(Put, PUT); +method_guard!(Delete, DELETE); +method_guard!(Head, HEAD); +method_guard!(Options, OPTIONS); +method_guard!(Connect, CONNECT); +method_guard!(Patch, PATCH); +method_guard!(Trace, TRACE); + +/// Creates a guard that matches if request contains given header name and value. +/// +/// # Examples +/// The handler below will be called when the request contains an `x-guarded` header with value +/// equal to `secret`. +/// ``` +/// use actix_web::{guard, web, HttpResponse}; +/// +/// web::route() +/// .guard(guard::Header("x-guarded", "secret")) +/// .to(|| HttpResponse::Ok()); +/// ``` +#[allow(non_snake_case)] +pub fn Header(name: &'static str, value: &'static str) -> impl Guard { HeaderGuard( header::HeaderName::try_from(name).unwrap(), header::HeaderValue::from_static(value), ) } -#[doc(hidden)] -pub struct HeaderGuard(header::HeaderName, header::HeaderValue); +struct HeaderGuard(header::HeaderName, header::HeaderValue); impl Guard for HeaderGuard { - fn check(&self, req: &RequestHead) -> bool { - if let Some(val) = req.headers.get(&self.0) { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + if let Some(val) = ctx.head().headers.get(&self.0) { return val == self.1; } + false } } -/// Return predicate that matches if request contains specified Host name. +/// Creates a guard that matches requests targetting a specific host. /// -/// ``` -/// use actix_web::{web, guard::Host, App, HttpResponse}; +/// # Matching Host +/// This guard will: +/// - match against the `Host` header, if present; +/// - fall-back to matching against the request target's host, if present; +/// - return false if host cannot be determined; /// -/// App::new().service( -/// web::resource("/index.html") -/// .guard(Host("www.rust-lang.org")) -/// .to(|| HttpResponse::MethodNotAllowed()) -/// ); +/// # Matching Scheme +/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using +/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent +/// the guard from matching successfully. +/// +/// # Examples +/// The [module-level documentation](self) has an example of virtual hosting using `Host` guards. +/// +/// The example below additionally guards on the host URI's scheme. This could allow routing to +/// different handlers for `http:` vs `https:` visitors; to redirect, for example. /// ``` -pub fn Host>(host: H) -> HostGuard { - HostGuard(host.as_ref().to_string(), None) +/// use actix_web::{web, guard::Host, HttpResponse}; +/// +/// web::scope("/admin") +/// .guard(Host("admin.rust-lang.org").scheme("https")) +/// .default_service(web::to(|| HttpResponse::Ok().body("admin connection is secure"))); +/// ``` +#[allow(non_snake_case)] +pub fn Host(host: impl AsRef) -> HostGuard { + HostGuard { + host: host.as_ref().to_string(), + scheme: None, + } } fn get_host_uri(req: &RequestHead) -> Option { - use core::str::FromStr; req.headers .get(header::HOST) .and_then(|host_value| host_value.to_str().ok()) .or_else(|| req.uri.host()) - .map(|host: &str| Uri::from_str(host).ok()) - .and_then(|host_success| host_success) + .and_then(|host| host.parse().ok()) } #[doc(hidden)] -pub struct HostGuard(String, Option); +pub struct HostGuard { + host: String, + scheme: Option, +} impl HostGuard { /// Set request scheme to match pub fn scheme>(mut self, scheme: H) -> HostGuard { - self.1 = Some(scheme.as_ref().to_string()); + self.scheme = Some(scheme.as_ref().to_string()); self } } impl Guard for HostGuard { - fn check(&self, req: &RequestHead) -> bool { - let req_host_uri = if let Some(uri) = get_host_uri(req) { - uri - } else { - return false; + fn check(&self, ctx: &GuardContext<'_>) -> bool { + // parse host URI from header or request target + let req_host_uri = match get_host_uri(ctx.head()) { + Some(uri) => uri, + + // no match if host cannot be determined + None => return false, }; - if let Some(uri_host) = req_host_uri.host() { - if self.0 != uri_host { - return false; - } - } else { - return false; + match req_host_uri.host() { + // fall through to scheme checks + Some(uri_host) if self.host == uri_host => {} + + // Either: + // - request's host does not match guard's host; + // - It was possible that the parsed URI from request target did not contain a host. + _ => return false, } - if let Some(ref scheme) = self.1 { + if let Some(ref scheme) = self.scheme { if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() { return scheme == req_host_uri_scheme; } + + // TODO: is the the correct behavior? + // falls through if scheme cannot be determined } + // all conditions passed true } } @@ -327,171 +445,201 @@ mod tests { use crate::test::TestRequest; #[test] - fn test_header() { + fn header_match() { let req = TestRequest::default() .insert_header((header::TRANSFER_ENCODING, "chunked")) - .to_http_request(); + .to_srv_request(); - let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(req.head())); + let hdr = Header("transfer-encoding", "chunked"); + assert!(hdr.check(&req.guard_ctx())); - let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(req.head())); + let hdr = Header("transfer-encoding", "other"); + assert!(!hdr.check(&req.guard_ctx())); - let pred = Header("content-type", "other"); - assert!(!pred.check(req.head())); + let hdr = Header("content-type", "chunked"); + assert!(!hdr.check(&req.guard_ctx())); + + let hdr = Header("content-type", "other"); + assert!(!hdr.check(&req.guard_ctx())); } #[test] - fn test_host() { + fn host_from_header() { let req = TestRequest::default() .insert_header(( header::HOST, header::HeaderValue::from_static("www.rust-lang.org"), )) - .to_http_request(); + .to_srv_request(); - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); + let host = Host("www.rust-lang.org"); + assert!(host.check(&req.guard_ctx())); - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); + let host = Host("www.rust-lang.org").scheme("https"); + assert!(host.check(&req.guard_ctx())); - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); + let host = Host("blog.rust-lang.org"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); + let host = Host("blog.rust-lang.org").scheme("https"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("crates.io"); - assert!(!pred.check(req.head())); + let host = Host("crates.io"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("localhost"); - assert!(!pred.check(req.head())); + let host = Host("localhost"); + assert!(!host.check(&req.guard_ctx())); } #[test] - fn test_host_scheme() { + fn host_without_header() { + let req = TestRequest::default() + .uri("www.rust-lang.org") + .to_srv_request(); + + let host = Host("www.rust-lang.org"); + assert!(host.check(&req.guard_ctx())); + + let host = Host("www.rust-lang.org").scheme("https"); + assert!(host.check(&req.guard_ctx())); + + let host = Host("blog.rust-lang.org"); + assert!(!host.check(&req.guard_ctx())); + + let host = Host("blog.rust-lang.org").scheme("https"); + assert!(!host.check(&req.guard_ctx())); + + let host = Host("crates.io"); + assert!(!host.check(&req.guard_ctx())); + + let host = Host("localhost"); + assert!(!host.check(&req.guard_ctx())); + } + + #[test] + fn host_scheme() { let req = TestRequest::default() .insert_header(( header::HOST, header::HeaderValue::from_static("https://www.rust-lang.org"), )) - .to_http_request(); + .to_srv_request(); - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); + let host = Host("www.rust-lang.org").scheme("https"); + assert!(host.check(&req.guard_ctx())); - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); + let host = Host("www.rust-lang.org"); + assert!(host.check(&req.guard_ctx())); - let pred = Host("www.rust-lang.org").scheme("http"); - assert!(!pred.check(req.head())); + let host = Host("www.rust-lang.org").scheme("http"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); + let host = Host("blog.rust-lang.org"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); + let host = Host("blog.rust-lang.org").scheme("https"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("crates.io").scheme("https"); - assert!(!pred.check(req.head())); + let host = Host("crates.io").scheme("https"); + assert!(!host.check(&req.guard_ctx())); - let pred = Host("localhost"); - assert!(!pred.check(req.head())); + let host = Host("localhost"); + assert!(!host.check(&req.guard_ctx())); } #[test] - fn test_host_without_header() { + fn method_guards() { + let get_req = TestRequest::get().to_srv_request(); + let post_req = TestRequest::post().to_srv_request(); + + assert!(Get().check(&get_req.guard_ctx())); + assert!(!Get().check(&post_req.guard_ctx())); + + assert!(Post().check(&post_req.guard_ctx())); + assert!(!Post().check(&get_req.guard_ctx())); + + let req = TestRequest::put().to_srv_request(); + assert!(Put().check(&req.guard_ctx())); + assert!(!Put().check(&get_req.guard_ctx())); + + let req = TestRequest::patch().to_srv_request(); + assert!(Patch().check(&req.guard_ctx())); + assert!(!Patch().check(&get_req.guard_ctx())); + + let r = TestRequest::delete().to_srv_request(); + assert!(Delete().check(&r.guard_ctx())); + assert!(!Delete().check(&get_req.guard_ctx())); + + let req = TestRequest::default().method(Method::HEAD).to_srv_request(); + assert!(Head().check(&req.guard_ctx())); + assert!(!Head().check(&get_req.guard_ctx())); + let req = TestRequest::default() - .uri("www.rust-lang.org") - .to_http_request(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); - - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); - - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); - - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); - - let pred = Host("crates.io"); - assert!(!pred.check(req.head())); - - let pred = Host("localhost"); - assert!(!pred.check(req.head())); - } - - #[test] - fn test_methods() { - let req = TestRequest::default().to_http_request(); - let req2 = TestRequest::default() - .method(Method::POST) - .to_http_request(); - - assert!(Get().check(req.head())); - assert!(!Get().check(req2.head())); - assert!(Post().check(req2.head())); - assert!(!Post().check(req.head())); - - let r = TestRequest::default().method(Method::PUT).to_http_request(); - assert!(Put().check(r.head())); - assert!(!Put().check(req.head())); - - let r = TestRequest::default() - .method(Method::DELETE) - .to_http_request(); - assert!(Delete().check(r.head())); - assert!(!Delete().check(req.head())); - - let r = TestRequest::default() - .method(Method::HEAD) - .to_http_request(); - assert!(Head().check(r.head())); - assert!(!Head().check(req.head())); - - let r = TestRequest::default() .method(Method::OPTIONS) - .to_http_request(); - assert!(Options().check(r.head())); - assert!(!Options().check(req.head())); + .to_srv_request(); + assert!(Options().check(&req.guard_ctx())); + assert!(!Options().check(&get_req.guard_ctx())); - let r = TestRequest::default() + let req = TestRequest::default() .method(Method::CONNECT) - .to_http_request(); - assert!(Connect().check(r.head())); - assert!(!Connect().check(req.head())); + .to_srv_request(); + assert!(Connect().check(&req.guard_ctx())); + assert!(!Connect().check(&get_req.guard_ctx())); - let r = TestRequest::default() - .method(Method::PATCH) - .to_http_request(); - assert!(Patch().check(r.head())); - assert!(!Patch().check(req.head())); - - let r = TestRequest::default() + let req = TestRequest::default() .method(Method::TRACE) - .to_http_request(); - assert!(Trace().check(r.head())); - assert!(!Trace().check(req.head())); + .to_srv_request(); + assert!(Trace().check(&req.guard_ctx())); + assert!(!Trace().check(&get_req.guard_ctx())); } #[test] - fn test_preds() { - let r = TestRequest::default() + fn aggregate_any() { + let req = TestRequest::default() .method(Method::TRACE) - .to_http_request(); + .to_srv_request(); - assert!(Not(Get()).check(r.head())); - assert!(!Not(Trace()).check(r.head())); + assert!(Any(Trace()).check(&req.guard_ctx())); + assert!(Any(Trace()).or(Get()).check(&req.guard_ctx())); + assert!(!Any(Get()).or(Get()).check(&req.guard_ctx())); + } - assert!(All(Trace()).and(Trace()).check(r.head())); - assert!(!All(Get()).and(Trace()).check(r.head())); + #[test] + fn aggregate_all() { + let req = TestRequest::default() + .method(Method::TRACE) + .to_srv_request(); - assert!(Any(Get()).or(Trace()).check(r.head())); - assert!(!Any(Get()).or(Get()).check(r.head())); + assert!(All(Trace()).check(&req.guard_ctx())); + assert!(All(Trace()).and(Trace()).check(&req.guard_ctx())); + assert!(!All(Trace()).and(Get()).check(&req.guard_ctx())); + } + + #[test] + fn nested_not() { + let req = TestRequest::default().to_srv_request(); + + let get = Get(); + assert!(get.check(&req.guard_ctx())); + + let not_get = Not(get); + assert!(!not_get.check(&req.guard_ctx())); + + let not_not_get = Not(not_get); + assert!(not_not_get.check(&req.guard_ctx())); + } + + #[test] + fn function_guard() { + let domain = "rust-lang.org".to_owned(); + let guard = fn_guard(|ctx| ctx.head().uri.host().unwrap().ends_with(&domain)); + + let req = TestRequest::default() + .uri("blog.rust-lang.org") + .to_srv_request(); + assert!(guard.check(&req.guard_ctx())); + + let req = TestRequest::default().uri("crates.io").to_srv_request(); + assert!(!guard.check(&req.guard_ctx())); } } diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 18dcaeefa..3ab908481 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -225,7 +225,7 @@ mod tests { .service(web::resource("/v1/something").to(HttpResponse::Ok)) .service( web::resource("/v2/something") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) @@ -261,7 +261,7 @@ mod tests { .service(web::resource("/v1/something").to(HttpResponse::Ok)) .service( web::resource("/v2/something") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) @@ -294,7 +294,7 @@ mod tests { let app = init_service( App::new().wrap(NormalizePath(TrailingSlash::Trim)).service( web::resource("/") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) @@ -318,7 +318,7 @@ mod tests { .service(web::resource("/v1/something/").to(HttpResponse::Ok)) .service( web::resource("/v2/something/") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) @@ -353,7 +353,7 @@ mod tests { .wrap(NormalizePath(TrailingSlash::Always)) .service( web::resource("/") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) @@ -378,7 +378,7 @@ mod tests { .service(web::resource("/v1/").to(HttpResponse::Ok)) .service( web::resource("/v2/something") - .guard(fn_guard(|req| req.uri.query() == Some("query=test"))) + .guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test"))) .to(HttpResponse::Ok), ), ) diff --git a/src/route.rs b/src/route.rs index 6d6fca4b7..0410b99dd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -65,9 +65,12 @@ pub struct RouteService { } impl RouteService { + // TODO: does this need to take &mut ? pub fn check(&self, req: &mut ServiceRequest) -> bool { - for f in self.guards.iter() { - if !f.check(req.head()) { + let guard_ctx = req.guard_ctx(); + + for guard in self.guards.iter() { + if !guard.check(&guard_ctx) { return false; } } @@ -90,6 +93,7 @@ impl Service for RouteService { impl Route { /// Add method guard to the route. /// + /// # Examples /// ``` /// # use actix_web::*; /// # fn main() { @@ -110,6 +114,7 @@ impl Route { /// Add guard to the route. /// + /// # Examples /// ``` /// # use actix_web::*; /// # fn main() { @@ -143,16 +148,13 @@ impl Route { /// format!("Welcome {}!", info.username) /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/{username}/index.html") // <- define path parameters - /// .route(web::get().to(index)) // <- register handler - /// ); - /// } + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) // <- register handler + /// ); /// ``` /// /// It is possible to use multiple extractors for one handler function. - /// /// ``` /// # use std::collections::HashMap; /// # use serde::Deserialize; @@ -164,16 +166,18 @@ impl Route { /// } /// /// /// extract path info using serde - /// async fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { + /// async fn index( + /// path: web::Path, + /// query: web::Query>, + /// body: web::Json + /// ) -> String { /// format!("Welcome {}!", path.username) /// } /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/{username}/index.html") // <- define path parameters - /// .route(web::get().to(index)) - /// ); - /// } + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) + /// ); /// ``` pub fn to(mut self, handler: F) -> Self where @@ -199,7 +203,7 @@ impl Route { /// type Error = Infallible; /// type Future = LocalBoxFuture<'static, Result>; /// - /// always_ready!(); + /// dev::always_ready!(); /// /// fn call(&self, req: ServiceRequest) -> Self::Future { /// let (req, _) = req.into_parts(); diff --git a/src/scope.rs b/src/scope.rs index 176e0d5a0..b4618bb6c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -538,12 +538,15 @@ impl Service for ScopeService { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { if let Some(ref guards) = guards { - for f in guards { - if !f.check(req.head()) { + let guard_ctx = req.guard_ctx(); + + for guard in guards { + if !guard.check(&guard_ctx) { return false; } } } + true }); diff --git a/src/service.rs b/src/service.rs index d5c381fa4..975556197 100644 --- a/src/service.rs +++ b/src/service.rs @@ -21,7 +21,7 @@ use cookie::{Cookie, ParseError as CookieParseError}; use crate::{ config::{AppConfig, AppService}, dev::ensure_leading_slash, - guard::Guard, + guard::{Guard, GuardContext}, info::ConnectionInfo, rmap::ResourceMap, Error, HttpRequest, HttpResponse, @@ -172,7 +172,7 @@ impl ServiceRequest { self.head().uri.path() } - /// Counterpart to [`HttpRequest::query_string`](super::HttpRequest::query_string()). + /// Counterpart to [`HttpRequest::query_string`]. #[inline] pub fn query_string(&self) -> &str { self.req.query_string() @@ -208,13 +208,13 @@ impl ServiceRequest { self.req.match_info() } - /// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()). + /// Counterpart to [`HttpRequest::match_name`]. #[inline] pub fn match_name(&self) -> Option<&str> { self.req.match_name() } - /// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()). + /// Counterpart to [`HttpRequest::match_pattern`]. #[inline] pub fn match_pattern(&self) -> Option { self.req.match_pattern() @@ -238,7 +238,7 @@ impl ServiceRequest { self.req.app_config() } - /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). + /// Counterpart to [`HttpRequest::app_data`]. #[inline] pub fn app_data(&self) -> Option<&T> { for container in self.req.inner.app_data.iter().rev() { @@ -250,19 +250,33 @@ impl ServiceRequest { None } - /// Counterpart to [`HttpRequest::conn_data`](super::HttpRequest::conn_data()). + /// Counterpart to [`HttpRequest::conn_data`]. #[inline] pub fn conn_data(&self) -> Option<&T> { self.req.conn_data() } + /// Counterpart to [`HttpRequest::req_data`]. + #[inline] + pub fn req_data(&self) -> Ref<'_, Extensions> { + self.req.req_data() + } + + /// Counterpart to [`HttpRequest::req_data_mut`]. + #[inline] + pub fn req_data_mut(&self) -> RefMut<'_, Extensions> { + self.req.req_data_mut() + } + #[cfg(feature = "cookies")] + #[inline] pub fn cookies(&self) -> Result>>, CookieParseError> { self.req.cookies() } /// Return request cookie. #[cfg(feature = "cookies")] + #[inline] pub fn cookie(&self, name: &str) -> Option> { self.req.cookie(name) } @@ -283,6 +297,14 @@ impl ServiceRequest { .app_data .push(extensions); } + + /// Creates a context object for use with a [guard](crate::guard). + /// + /// Useful if you are implementing + #[inline] + pub fn guard_ctx(&self) -> GuardContext<'_> { + GuardContext { req: self } + } } impl Resource for ServiceRequest { diff --git a/src/test/test_request.rs b/src/test/test_request.rs index 5c4de9084..fc42253d7 100644 --- a/src/test/test_request.rs +++ b/src/test/test_request.rs @@ -124,7 +124,7 @@ impl TestRequest { self } - /// Set HTTP Uri of this request + /// Set HTTP URI of this request pub fn uri(mut self, path: &str) -> Self { self.req.uri(path); self From 96a4dc9decf4947b60eaf9d33f29c328265eec36 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 28 Dec 2021 03:22:22 +0000 Subject: [PATCH 228/861] use modern signatures for awc `send_*` and `header` methods (#2553) --- awc/CHANGES.md | 5 ++ awc/src/any_body.rs | 93 +--------------------------------- awc/src/frozen.rs | 58 ++++++++------------- awc/src/middleware/redirect.rs | 4 +- awc/src/request.rs | 2 +- awc/src/responses/response.rs | 7 +-- awc/src/sender.rs | 45 ++++++---------- 7 files changed, 52 insertions(+), 162 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 200ad846a..06e94292a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553] +- `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553] +- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553] + +[#2553]: https://github.com/actix/actix-web/pull/2553 ## 3.0.0-beta.15 - 2021-12-27 diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs index 437216313..d09a943ab 100644 --- a/awc/src/any_body.rs +++ b/awc/src/any_body.rs @@ -1,17 +1,13 @@ use std::{ - borrow::Cow, fmt, mem, pin::Pin, task::{Context, Poll}, }; -use bytes::{Bytes, BytesMut}; -use futures_core::Stream; +use bytes::Bytes; use pin_project_lite::pin_project; -use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream}; - -use crate::BoxError; +use actix_http::body::{BodySize, BoxBody, MessageBody}; pin_project! { /// Represents various types of HTTP message body. @@ -160,91 +156,6 @@ impl fmt::Debug for AnyBody { } } -impl From<&'static str> for AnyBody { - fn from(string: &'static str) -> Self { - Self::Bytes { - body: Bytes::from_static(string.as_ref()), - } - } -} - -impl From<&'static [u8]> for AnyBody { - fn from(bytes: &'static [u8]) -> Self { - Self::Bytes { - body: Bytes::from_static(bytes), - } - } -} - -impl From> for AnyBody { - fn from(vec: Vec) -> Self { - Self::Bytes { - body: Bytes::from(vec), - } - } -} - -impl From for AnyBody { - fn from(string: String) -> Self { - Self::Bytes { - body: Bytes::from(string), - } - } -} - -impl From<&'_ String> for AnyBody { - fn from(string: &String) -> Self { - Self::Bytes { - body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)), - } - } -} - -impl From> for AnyBody { - fn from(string: Cow<'_, str>) -> Self { - match string { - Cow::Owned(s) => Self::from(s), - Cow::Borrowed(s) => Self::Bytes { - body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)), - }, - } - } -} - -impl From for AnyBody { - fn from(bytes: Bytes) -> Self { - Self::Bytes { body: bytes } - } -} - -impl From for AnyBody { - fn from(bytes: BytesMut) -> Self { - Self::Bytes { - body: bytes.freeze(), - } - } -} - -impl From> for AnyBody -where - S: Stream> + 'static, - E: Into + 'static, -{ - fn from(stream: SizedStream) -> Self { - AnyBody::new_boxed(stream) - } -} - -impl From> for AnyBody -where - S: Stream> + 'static, - E: Into + 'static, -{ - fn from(stream: BodyStream) -> Self { - AnyBody::new_boxed(stream) - } -} - #[cfg(test)] mod tests { use std::marker::PhantomPinned; diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 14ecf9f32..4023bd1c8 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, net, rc::Rc, time::Duration}; +use std::{net, rc::Rc, time::Duration}; use bytes::Bytes; use futures_core::Stream; @@ -7,7 +7,7 @@ use serde::Serialize; use actix_http::{ body::MessageBody, error::HttpError, - header::{HeaderMap, HeaderName, TryIntoHeaderValue}, + header::{HeaderMap, TryIntoHeaderPair}, Method, RequestHead, Uri, }; @@ -18,6 +18,7 @@ use crate::{ }; /// `FrozenClientRequest` struct represents cloneable client request. +/// /// It could be used to send same request multiple times. #[derive(Clone)] pub struct FrozenClientRequest { @@ -83,7 +84,7 @@ impl FrozenClientRequest { /// Send a streaming body. pub fn send_stream(&self, stream: S) -> SendClientRequest where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { RequestSender::Rc(self.head.clone(), None).send_stream( @@ -105,20 +106,14 @@ impl FrozenClientRequest { ) } - /// Create a `FrozenSendBuilder` with extra headers + /// Clones this `FrozenClientRequest`, returning a new one with extra headers added. pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder { FrozenSendBuilder::new(self.clone(), extra_headers) } - /// Create a `FrozenSendBuilder` with an extra header - pub fn extra_header(&self, key: K, value: V) -> FrozenSendBuilder - where - HeaderName: TryFrom, - >::Error: Into, - V: TryIntoHeaderValue, - { - self.extra_headers(HeaderMap::new()) - .extra_header(key, value) + /// Clones this `FrozenClientRequest`, returning a new one with the extra header added. + pub fn extra_header(&self, header: impl TryIntoHeaderPair) -> FrozenSendBuilder { + self.extra_headers(HeaderMap::new()).extra_header(header) } } @@ -139,29 +134,20 @@ impl FrozenSendBuilder { } /// Insert a header, it overrides existing header in `FrozenClientRequest`. - pub fn extra_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: TryIntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into_value() { - Ok(value) => { - self.extra_headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), + pub fn extra_header(mut self, header: impl TryIntoHeaderPair) -> Self { + match header.try_into_pair() { + Ok((key, value)) => { + self.extra_headers.insert(key, value); + } + + Err(err) => self.err = Some(err.into()), } + self } /// Complete request construction and send a body. - pub fn send_body(self, body: B) -> SendClientRequest - where - B: MessageBody + 'static, - { + pub fn send_body(self, body: impl MessageBody + 'static) -> SendClientRequest { if let Some(e) = self.err { return e.into(); } @@ -176,9 +162,9 @@ impl FrozenSendBuilder { } /// Complete request construction and send a json body. - pub fn send_json(self, value: &T) -> SendClientRequest { - if let Some(e) = self.err { - return e.into(); + pub fn send_json(self, value: impl Serialize) -> SendClientRequest { + if let Some(err) = self.err { + return err.into(); } RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json( @@ -191,7 +177,7 @@ impl FrozenSendBuilder { } /// Complete request construction and send an urlencoded body. - pub fn send_form(self, value: &T) -> SendClientRequest { + pub fn send_form(self, value: impl Serialize) -> SendClientRequest { if let Some(e) = self.err { return e.into(); } @@ -208,7 +194,7 @@ impl FrozenSendBuilder { /// Complete request construction and send a streaming body. pub fn send_stream(self, stream: S) -> SendClientRequest where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { if let Some(e) = self.err { diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 0ee969eee..704d2d79d 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -190,7 +190,9 @@ where let body_new = if is_redirect { // try to reuse body match body { - Some(ref bytes) => AnyBody::from(bytes.clone()), + Some(ref bytes) => AnyBody::Bytes { + body: bytes.clone(), + }, // TODO: should this be AnyBody::Empty or AnyBody::None. _ => AnyBody::empty(), } diff --git a/awc/src/request.rs b/awc/src/request.rs index 8824dd08a..8bcf1ee01 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -385,7 +385,7 @@ impl ClientRequest { /// Set an streaming body and generate `ClientRequest`. pub fn send_stream(self, stream: S) -> SendClientRequest where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { let slf = match self.prep_for_sending() { diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs index 6385aea19..0197265f1 100644 --- a/awc/src/responses/response.rs +++ b/awc/src/responses/response.rs @@ -7,8 +7,8 @@ use std::{ }; use actix_http::{ - error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions, - HttpMessage, Payload, ResponseHead, StatusCode, Version, + error::PayloadError, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, + Payload, ResponseHead, StatusCode, Version, }; use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; @@ -119,12 +119,13 @@ impl ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(&header::SET_COOKIE) { + for hdr in self.headers().get_all(&actix_http::header::SET_COOKIE) { let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); } self.extensions_mut().insert(Cookies(cookies)); } + Ok(Ref::map(self.extensions(), |ext| { &ext.get::().unwrap().0 })) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index edf41163d..cd30e571d 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -181,17 +181,14 @@ pub(crate) enum RequestSender { } impl RequestSender { - pub(crate) fn send_body( + pub(crate) fn send_body( self, addr: Option, response_decompress: bool, timeout: Option, config: &ClientConfig, - body: B, - ) -> SendClientRequest - where - B: MessageBody + 'static, - { + body: impl MessageBody + 'static, + ) -> SendClientRequest { let req = match self { RequestSender::Owned(head) => ConnectRequest::Client( RequestHeadType::Owned(head), @@ -210,15 +207,15 @@ impl RequestSender { SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) } - pub(crate) fn send_json( + pub(crate) fn send_json( mut self, addr: Option, response_decompress: bool, timeout: Option, config: &ClientConfig, - value: &T, + value: impl Serialize, ) -> SendClientRequest { - let body = match serde_json::to_string(value) { + let body = match serde_json::to_string(&value) { Ok(body) => body, Err(err) => return PrepForSendingError::Json(err).into(), }; @@ -227,22 +224,16 @@ impl RequestSender { return e.into(); } - self.send_body( - addr, - response_decompress, - timeout, - config, - AnyBody::from_message_body(body.into_bytes()), - ) + self.send_body(addr, response_decompress, timeout, config, body) } - pub(crate) fn send_form( + pub(crate) fn send_form( mut self, addr: Option, response_decompress: bool, timeout: Option, config: &ClientConfig, - value: &T, + value: impl Serialize, ) -> SendClientRequest { let body = match serde_urlencoded::to_string(value) { Ok(body) => body, @@ -250,19 +241,13 @@ impl RequestSender { }; // set content-type - if let Err(e) = + if let Err(err) = self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") { - return e.into(); + return err.into(); } - self.send_body( - addr, - response_decompress, - timeout, - config, - AnyBody::from_message_body(body.into_bytes()), - ) + self.send_body(addr, response_decompress, timeout, config, body) } pub(crate) fn send_stream( @@ -274,7 +259,7 @@ impl RequestSender { stream: S, ) -> SendClientRequest where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { self.send_body( @@ -282,7 +267,7 @@ impl RequestSender { response_decompress, timeout, config, - AnyBody::new_boxed(BodyStream::new(stream)), + BodyStream::new(stream), ) } @@ -293,7 +278,7 @@ impl RequestSender { timeout: Option, config: &ClientConfig, ) -> SendClientRequest { - self.send_body(addr, response_decompress, timeout, config, AnyBody::empty()) + self.send_body(addr, response_decompress, timeout, config, ()) } fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> From 0f5c876c6bb764635f27280056c47f90382bccf6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 28 Dec 2021 14:50:48 +0000 Subject: [PATCH 229/861] tweak guard docs --- src/guard.rs | 70 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/guard.rs b/src/guard.rs index fb3e4f243..ebda69cb9 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -10,16 +10,22 @@ //! object and returns a boolean; true if the request _should_ be handled by the guarded service //! or handler. This interface is defined by the [`Guard`] trait. //! -//! Commonly-used guards are provided in this module as well as way of creating a guard from a +//! Commonly-used guards are provided in this module as well as a way of creating a guard from a //! closure ([`fn_guard`]). The [`Not`], [`Any`], and [`All`] guards are noteworthy, as they can be //! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on //! services multiple times (which might have different combining behavior than you want). //! +//! There are shortcuts for routes with method guards in the [`web`](crate::web) module: +//! [`web::get()`](crate::web::get), [`web::post()`](crate::web::post), etc. The routes created by +//! the following calls are equivalent: +//! - `web::get()` (recommended form) +//! - `web::route().guard(guard::Get())` +//! //! Guards can not modify anything about the request. However, it is possible to store extra //! attributes in the request-local data container obtained with [`GuardContext::req_data_mut`]. //! -//! Guards can prevent resource definitions from overlapping (resulting in some inaccessible routes) -//! where they otherwise would when only considering paths. See the virtual hosting example below. +//! Guards can prevent resource definitions from overlapping which, when only considering paths, +//! would result in inaccessible routes. See the [`Host`] guard for an example of virtual hosting. //! //! # Examples //! In the following code, the `/guarded` resource has one defined route whose handler will only be @@ -36,31 +42,9 @@ //! ); //! ``` //! -//! Guards can be used to set up some form of [virtual hosting] within a single app. -//! Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard -//! definitions they become safe to use in this way. Without these host guards, only routes under -//! the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1` -//! and `localhost` as the `Host` guards. -//! ``` -//! use actix_web::{web, http::Method, guard, App, HttpResponse}; -//! -//! App::new() -//! .service( -//! web::scope("") -//! .guard(guard::Host("www.rust-lang.org")) -//! .default_service(web::to(|| HttpResponse::Ok().body("marketing site"))), -//! ) -//! .service( -//! web::scope("") -//! .guard(guard::Host("play.rust-lang.org")) -//! .default_service(web::to(|| HttpResponse::Ok().body("playground frontend"))), -//! ); -//! ``` -//! //! [`Scope`]: crate::Scope::guard() //! [`Resource`]: crate::Resource::guard() //! [`Route`]: crate::Route::guard() -//! [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting use std::{ cell::{Ref, RefMut}, @@ -373,6 +357,29 @@ impl Guard for HeaderGuard { /// .guard(Host("admin.rust-lang.org").scheme("https")) /// .default_service(web::to(|| HttpResponse::Ok().body("admin connection is secure"))); /// ``` +/// +/// The `Host` guard can be used to set up some form of [virtual hosting] within a single app. +/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard +/// definitions they become safe to use in this way. Without these host guards, only routes under +/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1` +/// and `localhost` as the `Host` guards. +/// ``` +/// use actix_web::{web, http::Method, guard, App, HttpResponse}; +/// +/// App::new() +/// .service( +/// web::scope("") +/// .guard(guard::Host("www.rust-lang.org")) +/// .default_service(web::to(|| HttpResponse::Ok().body("marketing site"))), +/// ) +/// .service( +/// web::scope("") +/// .guard(guard::Host("play.rust-lang.org")) +/// .default_service(web::to(|| HttpResponse::Ok().body("playground frontend"))), +/// ); +/// ``` +/// +/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting #[allow(non_snake_case)] pub fn Host(host: impl AsRef) -> HostGuard { HostGuard { @@ -642,4 +649,17 @@ mod tests { let req = TestRequest::default().uri("crates.io").to_srv_request(); assert!(!guard.check(&req.guard_ctx())); } + + #[test] + fn mega_nesting() { + let guard = fn_guard(|ctx| All(Not(Any(Not(Trace())))).check(ctx)); + + let req = TestRequest::default().to_srv_request(); + assert!(!guard.check(&req.guard_ctx())); + + let req = TestRequest::default() + .method(Method::TRACE) + .to_srv_request(); + assert!(guard.check(&req.guard_ctx())); + } } From 2b2de298001c7718952a7e8647f6b1a2736c7259 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 28 Dec 2021 14:52:43 +0000 Subject: [PATCH 230/861] never return port in `realip_remote_addr` (#2554) --- CHANGES.md | 6 ++ src/config.rs | 4 +- src/info.rs | 125 ++++++++++++++++++++++++--------------- src/middleware/logger.rs | 2 +- src/request.rs | 19 +++--- 5 files changed, 97 insertions(+), 59 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f925f3b94..b6d3b103d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,13 @@ - Some guards now return `impl Guard` and their concrete types are made private: `guard::{Header}` and all the method guards. [#2552] - The `Not` guard is now generic over the type of guard it wraps. [#2552] +### Fixed +- Rename `ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554] +- `ConnectionInfo::peer_addr` will not return the port number. [#2554] +- `ConnectionInfo::realip_remote_addr` will not return the port number if sourcing the IP from the peer's socket address. [#2554] + [#2552]: https://github.com/actix/actix-web/pull/2552 +[#2554]: https://github.com/actix/actix-web/pull/2554 ## 4.0.0-beta.16 - 2021-12-27 diff --git a/src/config.rs b/src/config.rs index cfa9a4ca3..d68374387 100644 --- a/src/config.rs +++ b/src/config.rs @@ -128,7 +128,7 @@ impl AppConfig { /// Server host name. /// - /// Host name is used by application router as a hostname for url generation. + /// Host name is used by application router as a hostname for URL generation. /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) /// documentation for more information. /// @@ -137,7 +137,7 @@ impl AppConfig { &self.host } - /// Returns true if connection is secure(https) + /// Returns true if connection is secure (i.e., running over `https:`). pub fn secure(&self) -> bool { self.secure } diff --git a/src/info.rs b/src/info.rs index 71194b24d..ce1ef97c6 100644 --- a/src/info.rs +++ b/src/info.rs @@ -67,7 +67,7 @@ fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option< pub struct ConnectionInfo { host: String, scheme: String, - remote_addr: Option, + peer_addr: Option, realip_remote_addr: Option, } @@ -134,67 +134,70 @@ impl ConnectionInfo { .or_else(|| first_header_value(req, &*X_FORWARDED_FOR)) .map(str::to_owned); - let remote_addr = req.peer_addr.map(|addr| addr.to_string()); + let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string()); ConnectionInfo { host, scheme, - remote_addr, + peer_addr, realip_remote_addr, } } + /// Real IP (remote address) of client that initiated request. + /// + /// The address is resolved through the following, in order: + /// - `Forwarded` header + /// - `X-Forwarded-For` header + /// - peer address of opened socket (same as [`remote_addr`](Self::remote_addr)) + /// + /// # Security + /// Do not use this function for security purposes unless you can be sure that the `Forwarded` + /// and `X-Forwarded-For` headers cannot be spoofed by the client. If you are running without a + /// proxy then [obtaining the peer address](Self::peer_addr) would be more appropriate. + #[inline] + pub fn realip_remote_addr(&self) -> Option<&str> { + self.realip_remote_addr + .as_deref() + .or_else(|| self.peer_addr.as_deref()) + } + + /// Returns serialized IP address of the peer connection. + /// + /// See [`HttpRequest::peer_addr`] for more details. + #[inline] + pub fn peer_addr(&self) -> Option<&str> { + self.peer_addr.as_deref() + } + + /// Hostname of the request. + /// + /// Hostname is resolved through the following, in order: + /// - `Forwarded` header + /// - `X-Forwarded-Host` header + /// - `Host` header + /// - request target / URI + /// - configured server hostname + #[inline] + pub fn host(&self) -> &str { + &self.host + } + /// Scheme of the request. /// - /// Scheme is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Proto - /// - Uri + /// Scheme is resolved through the following, in order: + /// - `Forwarded` header + /// - `X-Forwarded-Proto` header + /// - request target / URI #[inline] pub fn scheme(&self) -> &str { &self.scheme } - /// Hostname of the request. - /// - /// Hostname is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Host - /// - Host - /// - Uri - /// - Server hostname - pub fn host(&self) -> &str { - &self.host - } - - /// Remote address of the connection. - /// - /// Get remote_addr address from socket address. + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `peer_addr`.")] pub fn remote_addr(&self) -> Option<&str> { - self.remote_addr.as_deref() - } - - /// Real IP (remote address) of client that initiated request. - /// - /// The address is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-For - /// - remote_addr name of opened socket - /// - /// # Security - /// Do not use this function for security purposes, unless you can ensure the Forwarded and - /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket - /// address explicitly, use [`HttpRequest::peer_addr()`][peer_addr] instead. - /// - /// [peer_addr]: crate::web::HttpRequest::peer_addr() - #[inline] - pub fn realip_remote_addr(&self) -> Option<&str> { - self.realip_remote_addr - .as_deref() - .or_else(|| self.remote_addr.as_deref()) + self.peer_addr() } } @@ -209,7 +212,7 @@ impl FromRequest for ConnectionInfo { /// Extractor for peer's socket address. /// -/// Also see [`HttpRequest::peer_addr`]. +/// Also see [`HttpRequest::peer_addr`] and [`ConnectionInfo::peer_addr`]. /// /// # Examples /// ``` @@ -432,13 +435,37 @@ mod tests { #[actix_rt::test] async fn peer_addr_extract() { + let req = TestRequest::default().to_http_request(); + let res = PeerAddr::extract(&req).await; + assert!(res.is_err()); + let addr = "127.0.0.1:8080".parse().unwrap(); let req = TestRequest::default().peer_addr(addr).to_http_request(); let peer_addr = PeerAddr::extract(&req).await.unwrap(); assert_eq!(peer_addr, PeerAddr(addr)); + } + #[actix_rt::test] + async fn remote_address() { let req = TestRequest::default().to_http_request(); - let res = PeerAddr::extract(&req).await; - assert!(res.is_err()); + let res = ConnectionInfo::extract(&req).await.unwrap(); + assert!(res.peer_addr().is_none()); + + let addr = "127.0.0.1:8080".parse().unwrap(); + let req = TestRequest::default().peer_addr(addr).to_http_request(); + let conn_info = ConnectionInfo::extract(&req).await.unwrap(); + assert_eq!(conn_info.peer_addr().unwrap(), "127.0.0.1"); + } + + #[actix_rt::test] + async fn real_ip_from_socket_addr() { + let req = TestRequest::default().to_http_request(); + let res = ConnectionInfo::extract(&req).await.unwrap(); + assert!(res.realip_remote_addr().is_none()); + + let addr = "127.0.0.1:8080".parse().unwrap(); + let req = TestRequest::default().peer_addr(addr).to_http_request(); + let conn_info = ConnectionInfo::extract(&req).await.unwrap(); + assert_eq!(conn_info.realip_remote_addr().unwrap(), "127.0.0.1"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d7fdb234f..969cb0c10 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -547,7 +547,7 @@ impl FormatText { *self = FormatText::Str(s.to_string()); } FormatText::RemoteAddr => { - let s = if let Some(peer) = req.connection_info().remote_addr() { + let s = if let Some(peer) = req.connection_info().peer_addr() { FormatText::Str((*peer).to_string()) } else { FormatText::Str("-".to_string()) diff --git a/src/request.rs b/src/request.rs index 07fb4eb2d..b59369317 100644 --- a/src/request.rs +++ b/src/request.rs @@ -228,23 +228,28 @@ impl HttpRequest { self.app_state().rmap() } - /// Peer socket address. + /// Returns peer socket address. /// /// Peer address is the directly connected peer's socket address. If a proxy is used in front of /// the Actix Web server, then it would be address of this proxy. /// - /// To get client connection information `.connection_info()` should be used. + /// For expanded client connection information, use [`connection_info`] instead. /// - /// Will only return None when called in unit tests. + /// Will only return None when called in unit tests unless [`TestRequest::peer_addr`] is used. + /// + /// [`TestRequest::peer_addr`]: crate::test::TestRequest::peer_addr + /// [`connection_info`]: Self::connection_info #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr } - /// Get *ConnectionInfo* for the current request. + /// Returns connection info for the current request. /// - /// This method panics if request's extensions container is already - /// borrowed. + /// The return type, [`ConnectionInfo`], can also be used as an extractor. + /// + /// # Panics + /// Panics if request's extensions container is already borrowed. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { if !self.extensions().contains::() { @@ -252,7 +257,7 @@ impl HttpRequest { self.extensions_mut().insert(info); } - Ref::map(self.extensions(), |e| e.get().unwrap()) + Ref::map(self.extensions(), |data| data.get().unwrap()) } /// App config From 798e9911e948857e15df695a7691df2a146b1120 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 07:07:46 +0000 Subject: [PATCH 231/861] prepare awc release 3.0.0-beta.16 --- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 4 ++-- awc/README.md | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 06e94292a..212469873 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.16 - 2021-12-29 - `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553] - `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553] - Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8777ffa74..676a10895 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.15" +version = "3.0.0-beta.16" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.16", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.17", features = ["openssl"] } brotli2 = "0.3.2" env_logger = "0.9" diff --git a/awc/README.md b/awc/README.md index 582ecb18f..4916210e4 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.15)](https://docs.rs/awc/3.0.0-beta.15) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.16)](https://docs.rs/awc/3.0.0-beta.16) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.15/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.15) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.16/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.16) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 11d50d792b6569f190f9873f3cee5c5569b75e0f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 07:07:51 +0000 Subject: [PATCH 232/861] prepare actix-web release 4.0.0-beta.17 --- CHANGES.md | 5 ++++- Cargo.toml | 4 ++-- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 4 ++-- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 4 ++-- actix-web-codegen/Cargo.toml | 2 +- 10 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b6d3b103d..c870f10e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.17 - 2021-12-29 ### Added - `guard::GuardContext` for use with the `Guard` trait. [#2552] - `ServiceRequest::guard_ctx` for obtaining a guard context. [#2552] @@ -8,7 +11,7 @@ ### Changed - `Guard` trait now receives a `&GuardContext`. [#2552] - `guard::fn_guard` functions now receives a `&GuardContext`. [#2552] -- Some guards now return `impl Guard` and their concrete types are made private: `guard::{Header}` and all the method guards. [#2552] +- Some guards now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552] - The `Not` guard is now generic over the type of guard it wraps. [#2552] ### Fixed diff --git a/Cargo.toml b/Cargo.toml index b6ef184e0..1b85e8e75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.16" +version = "4.0.0-beta.17" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] @@ -107,7 +107,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.15", features = ["openssl"] } +awc = { version = "3.0.0-beta.16", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/README.md b/README.md index f9d388f8b..afe6b1f8e 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.16)](https://docs.rs/actix-web/4.0.0-beta.16) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.17)](https://docs.rs/actix-web/4.0.0-beta.17) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.16) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.17)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bbd4fee22..b7bb3fd07 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.17" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.16", default-features = false } +actix-web = { version = "4.0.0-beta.17", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,4 +44,4 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -actix-web = "4.0.0-beta.16" +actix-web = "4.0.0-beta.17" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 5c58978ea..2883a8f7e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.2" -awc = { version = "3.0.0-beta.15", default-features = false } +awc = { version = "3.0.0-beta.16", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.17" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9e587890b..9575f55e7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.16" +actix-web = "4.0.0-beta.17" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index de13133a1..4beddd0b8 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.16", default-features = false } +actix-web = { version = "4.0.0-beta.17", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index c7177a38c..c523a6566 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,8 +34,8 @@ actix-http-test = "3.0.0-beta.10" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.15", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.16", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3b792093a..719c563cb 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.17" -actix-web = { version = "4.0.0-beta.16", default-features = false } +actix-web = { version = "4.0.0-beta.17", default-features = false } bytes = "1" bytestring = "1" @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -awc = { version = "3.0.0-beta.15", default-features = false } +awc = { version = "3.0.0-beta.16", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 5250baa90..b014a47ae 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.10" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.16" +actix-web = "4.0.0-beta.17" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" From 9779010a5af14a7ccbe3668280f117de5345769a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 07:08:10 +0000 Subject: [PATCH 233/861] prepare actix-files release 0.6.0-beta.12 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index af6dcb415..65007c955 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.12 - 2021-12-29 +* No significant changes since `0.6.0-beta.11`. + + ## 0.6.0-beta.11 - 2021-12-27 * No significant changes since `0.6.0-beta.10`. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b7bb3fd07..adf4408ba 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.11" +version = "0.6.0-beta.12" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index db5c94d1e..3f310a607 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.11)](https://docs.rs/actix-files/0.6.0-beta.11) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.12)](https://docs.rs/actix-files/0.6.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.11/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.11) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.12/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.12) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From a87e01f0d139836e1d738529f67da8ec9e1059ed Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 08:59:15 +0000 Subject: [PATCH 234/861] bump msrv to 1.54 --- .github/workflows/ci.yml | 2 +- CHANGES.md | 2 ++ Cargo.toml | 1 - README.md | 4 +-- actix-files/CHANGES.md | 5 +-- actix-files/README.md | 4 +-- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 4 +-- actix-http/CHANGES.md | 1 + actix-http/README.md | 4 +-- actix-multipart/CHANGES.md | 3 +- actix-multipart/README.md | 4 +-- actix-router/CHANGES.md | 1 + actix-test/CHANGES.md | 3 +- actix-web-actors/CHANGES.md | 3 +- actix-web-actors/README.md | 4 +-- actix-web-codegen/CHANGES.md | 1 + actix-web-codegen/README.md | 4 +-- actix-web-codegen/tests/trybuild.rs | 2 +- .../trybuild/route-missing-method-fail.stderr | 6 ++-- awc/README.md | 2 +- clippy.toml | 2 +- scripts/bump | 2 +- src/error/internal.rs | 16 ++++------ src/guard.rs | 30 ++++++++--------- src/lib.rs | 2 +- src/web.rs | 32 +++++++++---------- 27 files changed, 74 insertions(+), 71 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe464bf27..2689804f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.52.0 # MSRV + - 1.54.0 # MSRV - stable - nightly diff --git a/CHANGES.md b/CHANGES.md index c870f10e7..83efa6f3b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.54. ## 4.0.0-beta.17 - 2021-12-29 diff --git a/Cargo.toml b/Cargo.toml index 1b85e8e75..44c58e494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,6 @@ language-tags = "0.3" once_cell = "1.5" log = "0.4" mime = "0.3" -paste = "1" pin-project-lite = "0.2.7" regex = "1.4" serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index afe6b1f8e..aac7818bd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.17)](https://docs.rs/actix-web/4.0.0-beta.17) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.17)
@@ -32,7 +32,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Includes an async [HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.52+ +- Runs on stable Rust 1.54+ ## Documentation diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 65007c955..c626fd3fb 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,14 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 0.6.0-beta.12 - 2021-12-29 -* No significant changes since `0.6.0-beta.11`. +- No significant changes since `0.6.0-beta.11`. ## 0.6.0-beta.11 - 2021-12-27 -* No significant changes since `0.6.0-beta.10`. +- No significant changes since `0.6.0-beta.10`. ## 0.6.0-beta.10 - 2021-12-11 diff --git a/actix-files/README.md b/actix-files/README.md index 3f310a607..41dd714d3 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.12)](https://docs.rs/actix-files/0.6.0-beta.12) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.12/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.12) @@ -15,4 +15,4 @@ - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 8c6a63b72..e83e95bd3 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 3.0.0-beta.10 - 2021-12-27 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 589c54c23..e2cdc0ba2 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http-test/3.0.0-beta.10) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.10) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http-test) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d74a754ac..6911d9969 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 3.0.0-beta.17 - 2021-12-27 diff --git a/actix-http/README.md b/actix-http/README.md index 223e18ceb..084753ac9 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.17)](https://docs.rs/actix-http/3.0.0-beta.17) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.17) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-http) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 ## Example diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index a9a1e8784..65fe51d44 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,10 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 0.4.0-beta.11 - 2021-12-27 -* No significant changes since `0.4.0-beta.10`. +- No significant changes since `0.4.0-beta.10`. ## 0.4.0-beta.10 - 2021-12-11 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index a9ee325ba..a773f5d52 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.11)](https://docs.rs/actix-multipart/0.4.0-beta.11) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.11/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.11) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-multipart) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 0a6a56359..c85d10e2a 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 0.5.0-beta.3 - 2021-12-17 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 2de0a69d6..9838c6f5f 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,10 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 0.1.0-beta.10 - 2021-12-27 -* No significant changes since `0.1.0-beta.9`. +- No significant changes since `0.1.0-beta.9`. ## 0.1.0-beta.9 - 2021-12-17 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 2fbbe7444..5c8091fbb 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,10 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 4.0.0-beta.9 - 2021-12-27 -* No significant changes since `4.0.0-beta.8`. +- No significant changes since `4.0.0-beta.8`. ## 4.0.0-beta.8 - 2021-12-11 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 232c81eac..0bd007e6a 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web-actors/4.0.0-beta.9) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9) @@ -14,4 +14,4 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-actors) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 0d881d303..5f8c0f259 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.54. ## 0.5.0-beta.6 - 2021-12-11 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index f05d3f22c..abb638cee 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.6)](https://docs.rs/actix-web-codegen/0.5.0-beta.6) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6) @@ -14,7 +14,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 ## Compile Testing diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index dd70cb7ca..b2d9ce186 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.52)] // MSRV +#[rustversion::stable(1.54)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index c36b090c0..b1cefafde 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -1,13 +1,13 @@ error: The #[route(..)] macro requires at least one `method` attribute - --> $DIR/route-missing-method-fail.rs:3:1 + --> tests/trybuild/route-missing-method-fail.rs:3:1 | 3 | #[route("/")] | ^^^^^^^^^^^^^ | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> $DIR/route-missing-method-fail.rs:12:55 + --> tests/trybuild/route-missing-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/awc/README.md b/awc/README.md index 4916210e4..2f8562181 100644 --- a/awc/README.md +++ b/awc/README.md @@ -12,7 +12,7 @@ - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) -- Minimum Supported Rust Version (MSRV): 1.52 +- Minimum Supported Rust Version (MSRV): 1.54 ## Example diff --git a/clippy.toml b/clippy.toml index cef91fde7..ece14b8d2 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.52" +msrv = "1.54" diff --git a/scripts/bump b/scripts/bump index 43cd8b8c7..1cd190e03 100755 --- a/scripts/bump +++ b/scripts/bump @@ -40,7 +40,7 @@ cat "$CHANGELOG_FILE" | # if word count of changelog chunk is 0 then insert filler changelog chunk if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then - echo "* No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE" + echo "- No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE" echo >>"$CHANGE_CHUNK_FILE" echo >>"$CHANGE_CHUNK_FILE" fi diff --git a/src/error/internal.rs b/src/error/internal.rs index 37195dc2e..1c6e343e3 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -118,15 +118,13 @@ where macro_rules! error_helper { ($name:ident, $status:ident) => { - paste::paste! { - #[doc = "Helper function that wraps any error and generates a `" $status "` response."] - #[allow(non_snake_case)] - pub fn $name(err: T) -> Error - where - T: fmt::Debug + fmt::Display + 'static, - { - InternalError::new(err, StatusCode::$status).into() - } + #[doc = concat!("Helper function that wraps any error and generates a `", stringify!($status), "` response.")] + #[allow(non_snake_case)] + pub fn $name(err: T) -> Error + where + T: fmt::Debug + fmt::Display + 'static, + { + InternalError::new(err, StatusCode::$status).into() } }; } diff --git a/src/guard.rs b/src/guard.rs index ebda69cb9..7a015d2da 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -270,22 +270,20 @@ impl Guard for MethodGuard { macro_rules! method_guard { ($method_fn:ident, $method_const:ident) => { - paste::paste! { - #[doc = " Creates a guard that matches the `" $method_const "` request method."] - /// - /// # Examples - #[doc = " The route in this example will only respond to `" $method_const "` requests."] - /// ``` - /// use actix_web::{guard, web, HttpResponse}; - /// - /// web::route() - #[doc = " .guard(guard::" $method_fn "())"] - /// .to(|| HttpResponse::Ok()); - /// ``` - #[allow(non_snake_case)] - pub fn $method_fn() -> impl Guard { - MethodGuard(HttpMethod::$method_const) - } + #[doc = concat!("Creates a guard that matches the `", stringify!($method_const), "` request method.")] + /// + /// # Examples + #[doc = concat!("The route in this example will only respond to `", stringify!($method_const), "` requests.")] + /// ``` + /// use actix_web::{guard, web, HttpResponse}; + /// + /// web::route() + #[doc = concat!(" .guard(guard::", stringify!($method_fn), "())")] + /// .to(|| HttpResponse::Ok()); + /// ``` + #[allow(non_snake_case)] + pub fn $method_fn() -> impl Guard { + MethodGuard(HttpMethod::$method_const) } }; } diff --git a/src/lib.rs b/src/lib.rs index 5f5b915b7..18f0d581d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://docs.rs/awc/) -//! * Runs on stable Rust 1.52+ +//! * Runs on stable Rust 1.54+ //! //! # Crate Features //! * `cookies` - cookies support (enabled by default) diff --git a/src/web.rs b/src/web.rs index 47bff36a3..e4339352b 100644 --- a/src/web.rs +++ b/src/web.rs @@ -86,23 +86,21 @@ pub fn route() -> Route { macro_rules! method_route { ($method_fn:ident, $method_const:ident) => { - paste::paste! { - #[doc = " Creates a new route with `" $method_const "` method guard."] - /// - /// # Examples - #[doc = " In this example, one `" $method_const " /{project_id}` route is set up:"] - /// ``` - /// use actix_web::{web, App, HttpResponse}; - /// - /// let app = App::new().service( - /// web::resource("/{project_id}") - #[doc = " .route(web::" $method_fn "().to(|| HttpResponse::Ok()))"] - /// - /// ); - /// ``` - pub fn $method_fn() -> Route { - method(Method::$method_const) - } + #[doc = concat!(" Creates a new route with `", stringify!($method_const), "` method guard.")] + /// + /// # Examples + #[doc = concat!(" In this example, one `", stringify!($method_const), " /{project_id}` route is set up:")] + /// ``` + /// use actix_web::{web, App, HttpResponse}; + /// + /// let app = App::new().service( + /// web::resource("/{project_id}") + #[doc = concat!(" .route(web::", stringify!($method_fn), "().to(|| HttpResponse::Ok()))")] + /// + /// ); + /// ``` + pub fn $method_fn() -> Route { + method(Method::$method_const) } }; } From 74738c63a728043f3d138f8f5bbbbb4e8c406d77 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Wed, 29 Dec 2021 11:03:25 +0100 Subject: [PATCH 235/861] Upgrade time dependency (via `cookie`) (#2555) --- CHANGES.md | 2 +- Cargo.toml | 2 +- awc/CHANGES.md | 3 ++- awc/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 83efa6f3b..d7b8045c7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,9 @@ ## Unreleased - 2021-xx-xx ### Changed +- `actix-web` has upgraded to `cookie` 0.16. This removes `actix-web`'s dependency on a version of `time` that was affected by RUSTSEC-2020-0071. `actix-web` still depends on a vulnerable version of `chrono` via `rcgen`, but `rcgen` is only used as a dev dependency therefore this does not affect end users. - Minimum supported Rust version (MSRV) is now 1.54. - ## 4.0.0-beta.17 - 2021-12-29 ### Added - `guard::GuardContext` for use with the `Guard` trait. [#2552] diff --git a/Cargo.toml b/Cargo.toml index 44c58e494..3f91c6f9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ actix-web-codegen = "0.5.0-beta.6" ahash = "0.7" bytes = "1" cfg-if = "1" -cookie = { version = "0.15", features = ["percent-encode"], optional = true } +cookie = { version = "0.16", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 212469873..0b344b96c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx - +### Changed +- `awc` has upgraded to `cookie` 0.16. This removes `awc`'s dependency on a version of `time` that was affected by RUSTSEC-2020-0071. `awc` still depends on a vulnerable version of `chrono` via `rcgen`, but `rcgen` is only used as a dev dependency therefore this does not affect end users. ## 3.0.0-beta.16 - 2021-12-29 - `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 676a10895..e3a7346b8 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -85,7 +85,7 @@ serde_json = "1.0" serde_urlencoded = "0.7" tokio = { version = "1.8.4", features = ["sync"] } -cookie = { version = "0.15", features = ["percent-encode"], optional = true } +cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } From 542c92c9a7f0107ce5e6d6a3f71f34a80eac4b19 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 10:06:36 +0000 Subject: [PATCH 236/861] tweak changelogs --- CHANGES.md | 9 ++++++++- awc/CHANGES.md | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d7b8045c7..9b2a4747a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,16 @@ ## Unreleased - 2021-xx-xx ### Changed -- `actix-web` has upgraded to `cookie` 0.16. This removes `actix-web`'s dependency on a version of `time` that was affected by RUSTSEC-2020-0071. `actix-web` still depends on a vulnerable version of `chrono` via `rcgen`, but `rcgen` is only used as a dev dependency therefore this does not affect end users. +- Update `cookie` dependency (re-exported) to `0.16`. [#2555] - Minimum supported Rust version (MSRV) is now 1.54. +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[#2555]: https://github.com/actix/actix-web/pull/2555 +[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + + ## 4.0.0-beta.17 - 2021-12-29 ### Added - `guard::GuardContext` for use with the `Guard` trait. [#2552] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0b344b96c..d01e78a61 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,7 +2,14 @@ ## Unreleased - 2021-xx-xx ### Changed -- `awc` has upgraded to `cookie` 0.16. This removes `awc`'s dependency on a version of `time` that was affected by RUSTSEC-2020-0071. `awc` still depends on a vulnerable version of `chrono` via `rcgen`, but `rcgen` is only used as a dev dependency therefore this does not affect end users. +- Update `cookie` dependency (re-exported) to `0.16`. [#2555] + +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[#2555]: https://github.com/actix/actix-web/pull/2555 +[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + ## 3.0.0-beta.16 - 2021-12-29 - `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553] From a80e93d6db560de06eb5c238c8b16253e20b929d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 10:17:11 +0000 Subject: [PATCH 237/861] prepare actix-web release 4.0.0-beta.18 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9b2a4747a..28dd6698b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.18 - 2021-12-29 ### Changed - Update `cookie` dependency (re-exported) to `0.16`. [#2555] - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/Cargo.toml b/Cargo.toml index 3f91c6f9f..0d2dfda88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.17" +version = "4.0.0-beta.18" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index aac7818bd..c97748c2f 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.17)](https://docs.rs/actix-web/4.0.0-beta.17) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.18)](https://docs.rs/actix-web/4.0.0-beta.18) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.17) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.18)
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index adf4408ba..c2acbc761 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.17" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.17", default-features = false } +actix-web = { version = "4.0.0-beta.18", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,4 +44,4 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -actix-web = "4.0.0-beta.17" +actix-web = "4.0.0-beta.18" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2883a8f7e..bd6baf743 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.17" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9575f55e7..7c9b28944 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.17" +actix-web = "4.0.0-beta.18" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 4beddd0b8..715512111 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.17", default-features = false } +actix-web = { version = "4.0.0-beta.18", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index c523a6566..3720869ff 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.10" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.16", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 719c563cb..2e96a68ae 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.17" -actix-web = { version = "4.0.0-beta.17", default-features = false } +actix-web = { version = "4.0.0-beta.18", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index b014a47ae..03ff4698f 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.10" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.17" +actix-web = "4.0.0-beta.18" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e3a7346b8..46a202b8e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.17", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.18", features = ["openssl"] } brotli2 = "0.3.2" env_logger = "0.9" From 6df4974234445753e42458df97e940b0949bbb76 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Dec 2021 10:17:28 +0000 Subject: [PATCH 238/861] prepare awc release 3.0.0-beta.17 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d2dfda88..7abcba5a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ url = "2.1" [dev-dependencies] actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.16", features = ["openssl"] } +awc = { version = "3.0.0-beta.17", features = ["openssl"] } brotli2 = "0.3.2" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bd6baf743..d973ce151 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.2" -awc = { version = "3.0.0-beta.16", default-features = false } +awc = { version = "3.0.0-beta.17", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 3720869ff..e7ac92e2e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.16", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.17", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 2e96a68ae..52ffca1ba 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -awc = { version = "3.0.0-beta.16", default-features = false } +awc = { version = "3.0.0-beta.17", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index d01e78a61..346e95af4 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.17 - 2021-12-29 ### Changed - Update `cookie` dependency (re-exported) to `0.16`. [#2555] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 46a202b8e..9c1f56f64 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.16" +version = "3.0.0-beta.17" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index 2f8562181..ace2b2eb5 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.16)](https://docs.rs/awc/3.0.0-beta.16) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.17)](https://docs.rs/awc/3.0.0-beta.17) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.16/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.16) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.17/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.17) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 231a24ef8d88ad30aeef503fcc00f3d4d5a1973c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 30 Dec 2021 07:11:35 +0000 Subject: [PATCH 239/861] improve application data docs --- src/app.rs | 1 + src/data.rs | 46 ++++++++++++++++++++++++++++++---------------- src/request.rs | 30 +++++++++++++++++++++++++----- src/resource.rs | 1 + src/scope.rs | 1 + 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/app.rs b/src/app.rs index 10868d18d..3fddc055b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -109,6 +109,7 @@ impl App { /// .route("/", web::get().to(handler)) /// }) /// ``` + #[doc(alias = "manage")] pub fn app_data(mut self, ext: U) -> Self { self.extensions.insert(ext); self diff --git a/src/data.rs b/src/data.rs index ef077e87c..986456ac0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -19,23 +19,32 @@ pub(crate) trait DataFactory { pub(crate) type FnDataFactory = Box LocalBoxFuture<'static, Result, ()>>>; -/// Application data. +/// Application data wrapper and extractor. /// -/// Application level data is a piece of arbitrary data attached to the app, scope, or resource. -/// Application data is available to all routes and can be added during the application -/// configuration process via `App::data()`. +/// # Setting Data +/// Data is set using the `app_data` methods on `App`, `Scope`, and `Resource`. If data is wrapped +/// in this `Data` type for those calls, it can be used as an extractor. /// -/// Application data can be accessed by using `Data` extractor where `T` is data type. +/// Note that `Data` should be constructed _outside_ the `HttpServer::new` closure if shared, +/// potentially mutable state is desired. `Data` is cheap to clone; internally, it uses an `Arc`. /// -/// **Note**: HTTP server accepts an application factory rather than an application instance. HTTP -/// server constructs an application instance for each thread, thus application data must be -/// constructed multiple times. If you want to share data between different threads, a shareable -/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send` -/// or `Sync`. Internally `Data` contains an `Arc`. +/// See also [`App::app_data`](crate::App::app_data), [`Scope::app_data`](crate::Scope::app_data), +/// and [`Resource::app_data`](crate::Resource::app_data). +/// +/// # Extracting `Data` +/// Since the Actix Web router layers application data, the returned object will reference the +/// "closest" instance of the type. For example, if an `App` stores a `u32`, a nested `Scope` +/// also stores a `u32`, and the delegated request handler falls within that `Scope`, then +/// extracting a `web::>` for that handler will return the `Scope`'s instance. +/// However, using the same router set up and a request that does not get captured by the `Scope`, +/// `web::>` would return the `App`'s instance. /// /// If route data is not set for a handler, using `Data` extractor would cause a `500 Internal /// Server Error` response. /// +/// See also [`HttpRequest::app_data`] +/// and [`ServiceRequest::app_data`](crate::dev::ServiceRequest::app_data). +/// /// # Unsized Data /// For types that are unsized, most commonly `dyn T`, `Data` can wrap these types by first /// constructing an `Arc` and using the `From` implementation to convert it. @@ -79,6 +88,7 @@ pub(crate) type FnDataFactory = /// .route("/index.html", web::get().to(index)) /// .route("/index-alt.html", web::get().to(index_alt)); /// ``` +#[doc(alias = "state")] #[derive(Debug)] pub struct Data(Arc); @@ -90,12 +100,12 @@ impl Data { } impl Data { - /// Get reference to inner app data. + /// Returns reference to inner `T`. pub fn get_ref(&self) -> &T { self.0.as_ref() } - /// Convert to the internal Arc + /// Unwraps to the internal `Arc` pub fn into_inner(self) -> Arc { self.0 } @@ -143,13 +153,17 @@ impl FromRequest for Data { ok(st.clone()) } else { log::debug!( - "Failed to construct App-level Data extractor. \ - Request path: {:?} (type: {})", - req.path(), + "Failed to construct Data extractor type: `{}`. For the Data extractor to work \ + correctly, wrap the data with `Data::new()` and pass it to `App::app_data()`. \ + Ensure that types align in both the set and retrieve calls. \ + Request path: {}", type_name::(), + req.path() ); + err(ErrorInternalServerError( - "App data is not configured, to configure construct it with web::Data::new() and pass it to App::app_data()", + "Requested application data is not configured. \ + View/enable debug logs for more details.", )) } } diff --git a/src/request.rs b/src/request.rs index b59369317..2580ed12c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -266,14 +266,34 @@ impl HttpRequest { self.app_state().config() } - /// Get an application data object stored with `App::data` or `App::app_data` - /// methods during application configuration. + /// Retrieves a piece of application state. /// - /// If `App::data` was used to store object, use `Data`: + /// Extracts any object stored with [`App::app_data()`](crate::App::app_data) (or the + /// counterpart methods on [`Scope`](crate::Scope::app_data) and + /// [`Resource`](crate::Resource::app_data)) during application configuration. /// - /// ```ignore - /// let opt_t = req.app_data::>(); + /// Since the Actix Web router layers application data, the returned object will reference the + /// "closest" instance of the type. For example, if an `App` stores a `u32`, a nested `Scope` + /// also stores a `u32`, and the delegated request handler falls within that `Scope`, then + /// calling `.app_data::()` on an `HttpRequest` within that handler will return the + /// `Scope`'s instance. However, using the same router set up and a request that does not get + /// captured by the `Scope`, `.app_data::()` would return the `App`'s instance. + /// + /// If the state was stored using the [`Data`] wrapper, then it must also be retrieved using + /// this same type. + /// + /// See also the [`Data`] extractor. + /// + /// # Examples + /// ```no_run + /// # use actix_web::{web, test::TestRequest}; + /// # let req = TestRequest::default().to_http_request(); + /// # type T = u32; + /// let opt_t: Option<&T> = req.app_data::>(); /// ``` + /// + /// [`Data`]: crate::web::Data + #[doc(alias = "state")] pub fn app_data(&self) -> Option<&T> { for container in self.inner.app_data.iter().rev() { if let Some(data) = container.get::() { diff --git a/src/resource.rs b/src/resource.rs index 564c4d3ef..8da0a8a85 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -195,6 +195,7 @@ where /// .route(web::get().to(handler)) /// ); /// ``` + #[doc(alias = "manage")] pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) diff --git a/src/scope.rs b/src/scope.rs index b4618bb6c..fa9807f42 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -154,6 +154,7 @@ where /// .route("/", web::get().to(handler)) /// ); /// ``` + #[doc(alias = "manage")] pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) From b4ff6addfe4856aae65ec6b4d4754d5dce49080b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 30 Dec 2021 07:15:57 +0000 Subject: [PATCH 240/861] use match name if possible in data debug log --- src/data.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/data.rs b/src/data.rs index 986456ac0..ce7b1fee6 100644 --- a/src/data.rs +++ b/src/data.rs @@ -153,16 +153,15 @@ impl FromRequest for Data { ok(st.clone()) } else { log::debug!( - "Failed to construct Data extractor type: `{}`. For the Data extractor to work \ + "Failed to extract `Data<{}>` for `{}` handler. For the Data extractor to work \ correctly, wrap the data with `Data::new()` and pass it to `App::app_data()`. \ - Ensure that types align in both the set and retrieve calls. \ - Request path: {}", + Ensure that types align in both the set and retrieve calls.", type_name::(), - req.path() + req.match_name().unwrap_or_else(|| req.path()) ); err(ErrorInternalServerError( - "Requested application data is not configured. \ + "Requested application data is not configured correctly. \ View/enable debug logs for more details.", )) } From 5dcb2502370c2e7a3fa5bc11a338925485dcde4d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 31 Dec 2021 07:53:53 +0000 Subject: [PATCH 241/861] fix doc test --- README.md | 2 +- src/request.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c97748c2f..3072ba1c0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.18)
-[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) +[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) ![downloads](https://img.shields.io/crates/d/actix-web.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/src/request.rs b/src/request.rs index 2580ed12c..cbec70a29 100644 --- a/src/request.rs +++ b/src/request.rs @@ -286,10 +286,10 @@ impl HttpRequest { /// /// # Examples /// ```no_run - /// # use actix_web::{web, test::TestRequest}; + /// # use actix_web::{test::TestRequest, web::Data}; /// # let req = TestRequest::default().to_http_request(); /// # type T = u32; - /// let opt_t: Option<&T> = req.app_data::>(); + /// let opt_t: Option<&Data> = req.app_data::>(); /// ``` /// /// [`Data`]: crate::web::Data From b7089245908f19fe03838ae2ef67929960aaee91 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 31 Dec 2021 08:38:58 +0000 Subject: [PATCH 242/861] only run nightly checks on master ci --- .github/workflows/ci-master.yml | 87 +++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 1 - 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 548ec21b7..b78617dc5 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -5,6 +5,93 @@ on: branches: [master] jobs: + build_and_test_nightly: + strategy: + fail-fast: false + matrix: + target: + - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } + - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } + - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } + version: + - nightly + + name: ${{ matrix.target.name }} / ${{ matrix.version }} + runs-on: ${{ matrix.target.os }} + + env: + CI: 1 + CARGO_INCREMENTAL: 0 + VCPKGRS_DYNAMIC: 1 + + steps: + - uses: actions/checkout@v2 + + # install OpenSSL on Windows + # TODO: GitHub actions docs state that OpenSSL is + # already installed on these Windows machines somewhere + - name: Set vcpkg root + if: matrix.target.triple == 'x86_64-pc-windows-msvc' + run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + - name: Install OpenSSL + if: matrix.target.triple == 'x86_64-pc-windows-msvc' + run: vcpkg install openssl:x64-windows + + - name: Install ${{ matrix.version }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Install cargo-hack + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + + - name: check minimal + uses: actions-rs/cargo@v1 + with: { command: ci-check-min } + + - name: check default + uses: actions-rs/cargo@v1 + with: { command: ci-check-default } + + - name: tests + timeout-minutes: 60 + run: | + cargo test --lib --tests -p=actix-router --all-features + cargo test --lib --tests -p=actix-http --all-features + cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls + cargo test --lib --tests -p=actix-web-codegen --all-features + cargo test --lib --tests -p=awc --all-features + cargo test --lib --tests -p=actix-http-test --all-features + cargo test --lib --tests -p=actix-test --all-features + cargo test --lib --tests -p=actix-files + cargo test --lib --tests -p=actix-multipart --all-features + cargo test --lib --tests -p=actix-web-actors --all-features + + - name: tests (io-uring) + if: matrix.target.os == 'ubuntu-latest' + timeout-minutes: 60 + run: > + sudo bash -c "ulimit -Sl 512 + && ulimit -Hl 512 + && PATH=$PATH:/usr/share/rust/.cargo/bin + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo-cache + ci_feature_powerset_check: name: Verify Feature Combinations runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2689804f0..f41aa972f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: version: - 1.54.0 # MSRV - stable - - nightly name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} From e890307091059c5598d348365ca4e8c41b64731d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 3 Jan 2022 13:17:57 +0000 Subject: [PATCH 243/861] Fix AcceptEncoding header (#2501) --- .github/workflows/bench.yml | 2 - CHANGES.md | 16 + actix-files/src/named.rs | 6 +- actix-http/CHANGES.md | 21 + actix-http/src/encoding/decoder.rs | 12 +- actix-http/src/encoding/encoder.rs | 31 +- actix-http/src/error.rs | 1 + actix-http/src/header/map.rs | 7 + actix-http/src/header/mod.rs | 7 - .../src/header/shared/content_encoding.rs | 51 +- actix-http/src/header/shared/quality.rs | 23 +- actix-http/src/header/shared/quality_item.rs | 14 +- awc/tests/test_client.rs | 14 +- scripts/ci-test | 20 +- src/dev.rs | 126 +++-- src/http/header/accept.rs | 65 +-- src/http/header/accept_charset.rs | 13 +- src/http/header/accept_encoding.rs | 414 ++++++++++++-- src/http/header/accept_language.rs | 64 +-- src/http/header/content_disposition.rs | 1 - src/http/header/encoding.rs | 78 ++- src/middleware/compress.rs | 328 +++-------- src/types/mod.rs | 15 +- src/types/payload.rs | 1 + src/web.rs | 5 +- tests/compression.rs | 313 +++++++++++ tests/fixtures/lorem.txt | 5 + tests/fixtures/lorem.txt.br | Bin 0 -> 905 bytes tests/fixtures/lorem.txt.gz | Bin 0 -> 920 bytes tests/fixtures/lorem.txt.xz | Bin 0 -> 1020 bytes tests/fixtures/lorem.txt.zst | Bin 0 -> 898 bytes tests/test_server.rs | 520 ++++++------------ tests/test_utils.rs | 76 +++ 33 files changed, 1360 insertions(+), 889 deletions(-) create mode 100644 tests/compression.rs create mode 100644 tests/fixtures/lorem.txt create mode 100644 tests/fixtures/lorem.txt.br create mode 100644 tests/fixtures/lorem.txt.gz create mode 100644 tests/fixtures/lorem.txt.xz create mode 100644 tests/fixtures/lorem.txt.zst create mode 100644 tests/test_utils.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 828d62561..a4b54ca7a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,8 +1,6 @@ name: Benchmark on: - pull_request: - types: [opened, synchronize, reopened] push: branches: - master diff --git a/CHANGES.md b/CHANGES.md index 28dd6698b..423ea9fdc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,22 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- `impl Hash` for `http::header::Encoding`. [#2501] +- `AcceptEncoding::negotiate()`. [#2501] + +### Changed +- `AcceptEncoding::preference` now returns `Option>`. [#2501] +- Rename methods `BodyEncoding::{encoding => encode_with, get_encoding => preferred_encoding}`. [#2501] +- `http::header::Encoding` now only represents `Content-Encoding` types. [#2501] + +### Fixed +- Auto-negotiation of content encoding is more fault-tolerant when using the `Compress` middleware. [#2501] + +### Removed +- `Compress::new`; restricting compression algorithm is done through feature flags. [#2501] + +[#2501]: https://github.com/actix/actix-web/pull/2501 ## 4.0.0-beta.18 - 2021-12-29 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 810988f0c..04e453580 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -420,7 +420,7 @@ impl NamedFile { } if let Some(current_encoding) = self.encoding { - res.encoding(current_encoding); + res.encode_with(current_encoding); } let reader = chunked::new_chunked_read(self.md.len(), 0, self.file); @@ -494,7 +494,7 @@ impl NamedFile { // default compressing if let Some(current_encoding) = self.encoding { - res.encoding(current_encoding); + res.encode_with(current_encoding); } if let Some(lm) = last_modified { @@ -517,7 +517,7 @@ impl NamedFile { length = ranges[0].length; offset = ranges[0].start; - res.encoding(ContentEncoding::Identity); + res.encode_with(ContentEncoding::Identity); res.insert_header(( header::CONTENT_RANGE, format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6911d9969..cbdccd93c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,29 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- `impl Eq` for `header::ContentEncoding`. [#2501] +- `impl Copy` for `QualityItem` where `T: Copy`. [#2501] +- `Quality::ZERO` equivalent to `q=0`. [#2501] +- `QualityItem::zero` that uses `Quality::ZERO`. [#2501] +- `ContentEncoding::to_header_value()`. [#2501] + +### Changed +- `Quality::MIN` is now the smallest non-zero value. [#2501] +- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501] +- Rename `ContentEncoding::{Br => Brotli}`. [#2501] - Minimum supported Rust version (MSRV) is now 1.54. +### Fixed +- `ContentEncoding::Identity` can now be parsed from a string. [#2501] +- A `Vary` header is now correctly sent along with compressed content. [#2501] + +### Removed +- `ContentEncoding::Auto` variant. [#2501] +- `ContentEncoding::is_compression()`. [#2501] + +[#2501]: https://github.com/actix/actix-web/pull/2501 + ## 3.0.0-beta.17 - 2021-12-27 ### Changes diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 0f519637a..da4b56c6a 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -47,9 +47,9 @@ where pub fn new(stream: S, encoding: ContentEncoding) -> Decoder { let decoder = match encoding { #[cfg(feature = "compress-brotli")] - ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(BrotliDecoder::new( - Writer::new(), - )))), + ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new( + BrotliDecoder::new(Writer::new()), + ))), #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ZlibDecoder::new(Writer::new()), @@ -165,7 +165,7 @@ enum ContentDecoder { #[cfg(feature = "compress-gzip")] Gzip(Box>), #[cfg(feature = "compress-brotli")] - Br(Box>), + Brotli(Box>), // We need explicit 'static lifetime here because ZstdDecoder need lifetime // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` #[cfg(feature = "compress-zstd")] @@ -176,7 +176,7 @@ impl ContentDecoder { fn feed_eof(&mut self) -> io::Result> { match self { #[cfg(feature = "compress-brotli")] - ContentDecoder::Br(ref mut decoder) => match decoder.flush() { + ContentDecoder::Brotli(ref mut decoder) => match decoder.flush() { Ok(()) => { let b = decoder.get_mut().take(); @@ -234,7 +234,7 @@ impl ContentDecoder { fn feed_data(&mut self, data: Bytes) -> io::Result> { match self { #[cfg(feature = "compress-brotli")] - ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { + ContentDecoder::Brotli(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; let b = decoder.get_mut().take(); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index b565bb2b5..f40e0e579 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -56,11 +56,10 @@ impl Encoder { } pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { - let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) + let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::NO_CONTENT - || encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto); + || encoding == ContentEncoding::Identity); // no need to compress an empty body if matches!(body.size(), BodySize::None) { @@ -72,8 +71,8 @@ impl Encoder { Err(body) => EncoderBody::Stream { body }, }; - if can_encode { - // Modify response body only if encoder is set + if should_encode { + // wrap body only if encoder is feature-enabled if let Some(enc) = ContentEncoder::encoder(encoding) { update_head(encoding, head); @@ -252,10 +251,10 @@ where } fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { - head.headers_mut().insert( - header::CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); + head.headers_mut() + .insert(header::CONTENT_ENCODING, encoding.to_header_value()); + head.headers_mut() + .insert(header::VARY, HeaderValue::from_static("accept-encoding")); head.no_chunking(false); } @@ -268,7 +267,7 @@ enum ContentEncoder { Gzip(GzEncoder), #[cfg(feature = "compress-brotli")] - Br(BrotliEncoder), + Brotli(BrotliEncoder), // Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we // use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`. @@ -292,8 +291,8 @@ impl ContentEncoder { ))), #[cfg(feature = "compress-brotli")] - ContentEncoding::Br => { - Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) + ContentEncoding::Brotli => { + Some(ContentEncoder::Brotli(BrotliEncoder::new(Writer::new(), 3))) } #[cfg(feature = "compress-zstd")] @@ -302,7 +301,7 @@ impl ContentEncoder { Some(ContentEncoder::Zstd(encoder)) } - _ => None, + ContentEncoding::Identity => None, } } @@ -310,7 +309,7 @@ impl ContentEncoder { pub(crate) fn take(&mut self) -> Bytes { match *self { #[cfg(feature = "compress-brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + ContentEncoder::Brotli(ref mut encoder) => encoder.get_mut().take(), #[cfg(feature = "compress-gzip")] ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), @@ -326,7 +325,7 @@ impl ContentEncoder { fn finish(self) -> Result { match self { #[cfg(feature = "compress-brotli")] - ContentEncoder::Br(encoder) => match encoder.finish() { + ContentEncoder::Brotli(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, @@ -354,7 +353,7 @@ impl ContentEncoder { fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { #[cfg(feature = "compress-brotli")] - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { + ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { trace!("Error decoding br encoding: {}", err); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 3d2a918f4..cdf495c45 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -250,6 +250,7 @@ impl From for Response { /// A set of errors that can occur running blocking tasks in thread pool. #[derive(Debug, Display, Error)] #[display(fmt = "Blocking thread pool is gone")] +// TODO: non-exhaustive pub struct BlockingError; /// A set of errors that can occur during payload parsing. diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 478867ed0..5cf4ba2fa 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -605,6 +605,13 @@ impl<'a> IntoIterator for &'a HeaderMap { } } +/// Convert `http::HeaderMap` to our `HeaderMap`. +impl From for HeaderMap { + fn from(mut map: http::HeaderMap) -> HeaderMap { + HeaderMap::from_drain(map.drain()) + } +} + /// Iterator over removed, owned values with the same associated name. /// /// Returned from methods that remove or replace items. See [`HeaderMap::insert`] diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index dd4f06106..4e9140db4 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -57,13 +57,6 @@ pub trait Header: TryIntoHeaderValue { fn parse(msg: &M) -> Result; } -/// Convert `http::HeaderMap` to our `HeaderMap`. -impl From for HeaderMap { - fn from(mut map: http::HeaderMap) -> HeaderMap { - HeaderMap::from_drain(map.drain()) - } -} - /// This encode set is used for HTTP header values and is defined at /// . pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index 68511a8ee..ce011f107 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -20,14 +20,16 @@ pub struct ContentEncodingParseError; /// See [IANA HTTP Content Coding Registry]. /// /// [IANA HTTP Content Coding Registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation. - Auto, + /// Indicates the no-op identity encoding. + /// + /// I.e., no compression or modification. + Identity, /// A format using the Brotli algorithm. - Br, + Brotli, /// A format using the zlib structure with deflate algorithm. Deflate, @@ -37,27 +39,30 @@ pub enum ContentEncoding { /// Zstd algorithm. Zstd, - - /// Indicates the identity function (i.e. no compression, nor modification). - Identity, } impl ContentEncoding { - /// Is the content compressed? - #[inline] - pub const fn is_compression(self) -> bool { - matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) - } - /// Convert content encoding to string. #[inline] pub const fn as_str(self) -> &'static str { match self { - ContentEncoding::Br => "br", + ContentEncoding::Brotli => "br", ContentEncoding::Gzip => "gzip", ContentEncoding::Deflate => "deflate", ContentEncoding::Zstd => "zstd", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", + ContentEncoding::Identity => "identity", + } + } + + /// Convert content encoding to header value. + #[inline] + pub const fn to_header_value(self) -> HeaderValue { + match self { + ContentEncoding::Brotli => HeaderValue::from_static("br"), + ContentEncoding::Gzip => HeaderValue::from_static("gzip"), + ContentEncoding::Deflate => HeaderValue::from_static("deflate"), + ContentEncoding::Zstd => HeaderValue::from_static("zstd"), + ContentEncoding::Identity => HeaderValue::from_static("identity"), } } } @@ -71,16 +76,18 @@ impl Default for ContentEncoding { impl FromStr for ContentEncoding { type Err = ContentEncodingParseError; - fn from_str(val: &str) -> Result { - let val = val.trim(); + fn from_str(enc: &str) -> Result { + let enc = enc.trim(); - if val.eq_ignore_ascii_case("br") { - Ok(ContentEncoding::Br) - } else if val.eq_ignore_ascii_case("gzip") { + if enc.eq_ignore_ascii_case("br") { + Ok(ContentEncoding::Brotli) + } else if enc.eq_ignore_ascii_case("gzip") { Ok(ContentEncoding::Gzip) - } else if val.eq_ignore_ascii_case("deflate") { + } else if enc.eq_ignore_ascii_case("deflate") { Ok(ContentEncoding::Deflate) - } else if val.eq_ignore_ascii_case("zstd") { + } else if enc.eq_ignore_ascii_case("identity") { + Ok(ContentEncoding::Identity) + } else if enc.eq_ignore_ascii_case("zstd") { Ok(ContentEncoding::Zstd) } else { Err(ContentEncodingParseError) diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs index c2f08edc2..c80dd0a8e 100644 --- a/actix-http/src/header/shared/quality.rs +++ b/actix-http/src/header/shared/quality.rs @@ -27,7 +27,8 @@ const MAX_QUALITY_FLOAT: f32 = 1.0; /// /// assert_eq!(q(0.42).to_string(), "0.42"); /// assert_eq!(q(1.0).to_string(), "1"); -/// assert_eq!(Quality::MIN.to_string(), "0"); +/// assert_eq!(Quality::MIN.to_string(), "0.001"); +/// assert_eq!(Quality::ZERO.to_string(), "0"); /// ``` /// /// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1 @@ -38,8 +39,11 @@ impl Quality { /// The maximum quality value, equivalent to `q=1.0`. pub const MAX: Quality = Quality(MAX_QUALITY_INT); - /// The minimum quality value, equivalent to `q=0.0`. - pub const MIN: Quality = Quality(0); + /// The minimum, non-zero quality value, equivalent to `q=0.001`. + pub const MIN: Quality = Quality(1); + + /// The zero quality value, equivalent to `q=0.0`. + pub const ZERO: Quality = Quality(0); /// Converts a float in the range 0.0–1.0 to a `Quality`. /// @@ -51,7 +55,7 @@ impl Quality { // Check that `value` is within range should be done before calling this method. // Just in case, this debug_assert should catch if we were forgetful. debug_assert!( - (0.0f32..=1.0f32).contains(&value), + (0.0..=MAX_QUALITY_FLOAT).contains(&value), "q value must be between 0.0 and 1.0" ); @@ -154,10 +158,13 @@ impl TryFrom for Quality { /// let q1 = q(1.0); /// assert_eq!(q1, Quality::MAX); /// -/// let q2 = q(0.0); +/// let q2 = q(0.001); /// assert_eq!(q2, Quality::MIN); /// -/// let q3 = q(0.42); +/// let q3 = q(0.0); +/// assert_eq!(q3, Quality::ZERO); +/// +/// let q4 = q(0.42); /// ``` /// /// An out-of-range `f32` quality will panic. @@ -185,6 +192,10 @@ mod tests { #[test] fn display_output() { + assert_eq!(Quality::ZERO.to_string(), "0"); + assert_eq!(Quality::MIN.to_string(), "0.001"); + assert_eq!(Quality::MAX.to_string(), "1"); + assert_eq!(q(0.0).to_string(), "0"); assert_eq!(q(1.0).to_string(), "1"); assert_eq!(q(0.001).to_string(), "0.001"); diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index c9eee7d9d..71a3bdd53 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -31,7 +31,7 @@ use super::Quality; /// let q_item_fallback: QualityItem = "abc;q=0.1".parse().unwrap(); /// assert!(q_item > q_item_fallback); /// ``` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct QualityItem { /// The wrapped contents of the field. pub item: T, @@ -53,10 +53,15 @@ impl QualityItem { Self::new(item, Quality::MAX) } - /// Constructs a new `QualityItem` from an item, using the minimum q-value. + /// Constructs a new `QualityItem` from an item, using the minimum, non-zero q-value. pub fn min(item: T) -> Self { Self::new(item, Quality::MIN) } + + /// Constructs a new `QualityItem` from an item, using zero q-value of zero. + pub fn zero(item: T) -> Self { + Self::new(item, Quality::ZERO) + } } impl PartialOrd for QualityItem { @@ -73,7 +78,10 @@ impl fmt::Display for QualityItem { // q-factor value is implied for max value Quality::MAX => Ok(()), - Quality::MIN => f.write_str("; q=0"), + // fast path for zero + Quality::ZERO => f.write_str("; q=0"), + + // quality formatting is already using itoa q => write!(f, "; q={}", q), } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c453a768d..6eb6800d3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -473,7 +473,7 @@ async fn test_no_decompress() { .wrap(actix_web::middleware::Compress::default()) .service(web::resource("/").route(web::to(|| { let mut res = HttpResponse::Ok().body(STR); - res.encoding(header::ContentEncoding::Gzip); + res.encode_with(header::ContentEncoding::Gzip); res }))) }); @@ -644,7 +644,9 @@ async fn test_client_brotli_encoding_large_random() { async fn test_client_deflate_encoding() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) + HttpResponse::Ok() + .encode_with(ContentEncoding::Brotli) + .body(body) })) }); @@ -667,7 +669,9 @@ async fn test_client_deflate_encoding_large_random() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok().encoding(ContentEncoding::Br).body(body) + HttpResponse::Ok() + .encode_with(ContentEncoding::Brotli) + .body(body) })) }); @@ -685,7 +689,7 @@ async fn test_client_streaming_explicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() - .encoding(ContentEncoding::Identity) + .encode_with(ContentEncoding::Identity) .streaming(body) })) }); @@ -710,7 +714,7 @@ async fn test_body_streaming_implicit() { }); HttpResponse::Ok() - .encoding(ContentEncoding::Gzip) + .encode_with(ContentEncoding::Gzip) .streaming(Box::pin(body)) })) }); diff --git a/scripts/ci-test b/scripts/ci-test index 3ab229665..567012d33 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -12,16 +12,16 @@ save_exit_code() { [ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT } -save_exit_code cargo test --lib --tests -p=actix-router --all-features -save_exit_code cargo test --lib --tests -p=actix-http --all-features -save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls -save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -save_exit_code cargo test --lib --tests -p=awc --all-features -save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -save_exit_code cargo test --lib --tests -p=actix-test --all-features -save_exit_code cargo test --lib --tests -p=actix-files -save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features +save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls +save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture save_exit_code cargo test --workspace --doc diff --git a/src/dev.rs b/src/dev.rs index bb1385bde..b15d15747 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -20,11 +20,9 @@ pub use crate::info::{ConnectionInfo, PeerAddr}; pub use crate::rmap::ResourceMap; pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; -pub use crate::types::form::UrlEncoded; -pub use crate::types::json::JsonBody; -pub use crate::types::readlines::Readlines; +pub use crate::types::{JsonBody, Readlines, UrlEncoded}; -use crate::http::header::ContentEncoding; +use crate::{http::header::ContentEncoding, HttpMessage as _}; use actix_router::Patterns; @@ -47,59 +45,109 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { patterns } -/// Helper trait that allows to set specific encoding for response. +/// Helper trait for managing response encoding. +/// +/// Use `pre_encoded_with` to flag response as already encoded. For example, when serving a Gzip +/// compressed file from disk. pub trait BodyEncoding { /// Get content encoding - fn get_encoding(&self) -> Option; + fn preferred_encoding(&self) -> Option; - /// Set content encoding + /// Set content encoding to use. /// - /// Must be used with [`crate::middleware::Compress`] to take effect. - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; + /// Must be used with [`Compress`] to take effect. + /// + /// [`Compress`]: crate::middleware::Compress + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self; + + // /// Flags that a file already is encoded so that [`Compress`] does not modify it. + // /// + // /// Effectively a shortcut for `compress_with("identity")` + // /// plus `insert_header(ContentEncoding, encoding)`. + // /// + // /// [`Compress`]: crate::middleware::Compress + // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self; } -impl BodyEncoding for actix_http::ResponseBuilder { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } +struct CompressWith(ContentEncoding); - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } -} - -struct Enc(ContentEncoding); - -impl BodyEncoding for actix_http::Response { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } -} +// TODO: add or delete this +// struct PreCompressed(ContentEncoding); impl BodyEncoding for crate::HttpResponseBuilder { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) + fn preferred_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) } - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(CompressWith(encoding)); self } + + // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { + // self.extensions_mut().insert(PreCompressed(encoding)); + // self + // } } impl BodyEncoding for crate::HttpResponse { - fn get_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) + fn preferred_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) } - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(CompressWith(encoding)); + self + } + + // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { + // self.extensions_mut().insert(PreCompressed(encoding)); + // self + // } +} + +impl BodyEncoding for ServiceResponse { + fn preferred_encoding(&self) -> Option { + self.request() + .extensions() + .get::() + .map(|enc| enc.0) + } + + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { + self.request() + .extensions_mut() + .insert(CompressWith(encoding)); + self + } + + // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { + // self.request() + // .extensions_mut() + // .insert(PreCompressed(encoding)); + // self + // } +} + +// TODO: remove these impls ? +impl BodyEncoding for actix_http::ResponseBuilder { + fn preferred_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(CompressWith(encoding)); + self + } +} + +impl BodyEncoding for actix_http::Response { + fn preferred_encoding(&self) -> Option { + self.extensions().get::().map(|enc| enc.0) + } + + fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(CompressWith(encoding)); self } } diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index c61e6ab49..368a05bb2 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -147,6 +147,39 @@ impl Accept { Accept(vec![QualityItem::max(mime::TEXT_HTML)]) } + // TODO: method for getting best content encoding based on q-factors, available from server side + // and if none are acceptable return None + + /// Extracts the most preferable mime type, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first mime type is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained + /// list is empty. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn preference(&self) -> Mime { + use actix_http::header::Quality; + + let mut max_item = None; + let mut max_pref = Quality::ZERO; + + // uses manual max lookup loop since we want the first occurrence in the case of same + // preference but `Iterator::max_by_key` would give us the last occurrence + + for pref in &self.0 { + // only change if strictly greater + // equal items, even while unsorted, still have higher preference if they appear first + if pref.quality > max_pref { + max_pref = pref.quality; + max_item = Some(pref.item.clone()); + } + } + + max_item.unwrap_or(mime::STAR_STAR) + } + /// Returns a sorted list of mime types from highest to lowest preference, accounting for /// [q-factor weighting] and specificity. /// @@ -196,36 +229,6 @@ impl Accept { types.into_iter().map(|qitem| qitem.item).collect() } - - /// Extracts the most preferable mime type, accounting for [q-factor weighting]. - /// - /// If no q-factors are provided, the first mime type is chosen. Note that items without - /// q-factors are given the maximum preference value. - /// - /// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained - /// list is empty. - /// - /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 - pub fn preference(&self) -> Mime { - use actix_http::header::Quality; - - let mut max_item = None; - let mut max_pref = Quality::MIN; - - // uses manual max lookup loop since we want the first occurrence in the case of same - // preference but `Iterator::max_by_key` would give us the last occurrence - - for pref in &self.0 { - // only change if strictly greater - // equal items, even while unsorted, still have higher preference if they appear first - if pref.quality > max_pref { - max_pref = pref.quality; - max_item = Some(pref.item.clone()); - } - } - - max_item.unwrap_or(mime::STAR_STAR) - } } #[cfg(test)] @@ -239,7 +242,7 @@ mod tests { assert!(test.ranked().is_empty()); let test = Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]); - assert_eq!(test.ranked(), vec!(mime::APPLICATION_JSON)); + assert_eq!(test.ranked(), vec![mime::APPLICATION_JSON]); let test = Accept(vec![ QualityItem::max(mime::TEXT_HTML), diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index c8b918c91..c7f7e1a68 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,8 +1,7 @@ -use super::{Charset, QualityItem, ACCEPT_CHARSET}; +use super::{common_header, Charset, QualityItem, ACCEPT_CHARSET}; -crate::http::header::common_header! { - /// `Accept-Charset` header, defined in - /// [RFC 7231 §5.3.3](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3) +common_header! { + /// `Accept-Charset` header, defined in [RFC 7231 §5.3.3]. /// /// The `Accept-Charset` header field can be sent by a user agent to /// indicate what charsets are acceptable in textual response content. @@ -52,10 +51,12 @@ crate::http::header::common_header! { /// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))]) /// ); /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ + /// + /// [RFC 7231 §5.3.3]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3 + (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)* test_parse_and_format { // Test case from RFC - crate::http::header::common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 828a0533c..8c35179b6 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -1,17 +1,15 @@ -use actix_http::header::QualityItem; +use std::collections::HashSet; -use super::{common_header, Encoding}; +use super::{common_header, ContentEncoding, Encoding, Preference, Quality, QualityItem}; use crate::http::header; common_header! { /// `Accept-Encoding` header, defined /// in [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4) /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. + /// The `Accept-Encoding` header field can be used by user agents to indicate what response + /// content-codings are acceptable in the response. An `identity` token is used as a synonym + /// for "no encoding" in order to communicate when no encoding is preferred. /// /// # ABNF /// ```plain @@ -29,11 +27,11 @@ common_header! { /// # Examples /// ``` /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem}; + /// use actix_web::http::header::{AcceptEncoding, Encoding, Preference, QualityItem}; /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// AcceptEncoding(vec![QualityItem::max(Encoding::Chunked)]) + /// AcceptEncoding(vec![QualityItem::max(Preference::Specific(Encoding::gzip()))]) /// ); /// ``` /// @@ -44,40 +42,388 @@ common_header! { /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptEncoding(vec![ - /// QualityItem::max(Encoding::Chunked), - /// QualityItem::max(Encoding::Gzip), - /// QualityItem::max(Encoding::Deflate), + /// "gzip".parse().unwrap(), + /// "br".parse().unwrap(), /// ]) /// ); /// ``` - /// - /// ``` - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.insert_header( - /// AcceptEncoding(vec![ - /// QualityItem::max(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(0.60)), - /// QualityItem::min(Encoding::EncodingExt("*".to_owned())), - /// ]) - /// ); - /// ``` - (AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem)* + (AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem>)* test_parse_and_format { - // From the RFC - common_header_test!(test1, vec![b"compress, gzip"]); - common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - common_header_test!(test3, vec![b"*"]); + common_header_test!(no_headers, vec![b""; 0], Some(AcceptEncoding(vec![]))); + common_header_test!(empty_header, vec![b""; 1], Some(AcceptEncoding(vec![]))); + + common_header_test!( + order_of_appearance, + vec![b"br, gzip"], + Some(AcceptEncoding(vec![ + QualityItem::max(Preference::Specific(Encoding::brotli())), + QualityItem::max(Preference::Specific(Encoding::gzip())), + ])) + ); + + common_header_test!(any, vec![b"*"], Some(AcceptEncoding(vec![ + QualityItem::max(Preference::Any), + ]))); // Note: Removed quality 1 from gzip - common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); + common_header_test!(implicit_quality, vec![b"gzip, identity; q=0.5, *;q=0"]); // Note: Removed quality 1 from gzip - common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + common_header_test!(implicit_quality_out_of_order, vec![b"compress;q=0.5, gzip"]); + + common_header_test!( + only_gzip_no_identity, + vec![b"gzip, *; q=0"], + Some(AcceptEncoding(vec![ + QualityItem::max(Preference::Specific(Encoding::gzip())), + QualityItem::zero(Preference::Any), + ])) + ); } } -// TODO: shortcut for EncodingExt(*) = Any +impl AcceptEncoding { + /// Selects the most acceptable encoding according to client preference and supported types. + /// + /// The "identity" encoding is not assumed and should be included in the `supported` iterator + /// if a non-encoded representation can be selected. + /// + /// If `None` is returned, this indicates that none of the supported encodings are acceptable to + /// the client. The caller should generate a 406 Not Acceptable response (unencoded) that + /// includes the server's supported encodings in the body plus a [`Vary`] header. + /// + /// [`Vary`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary + pub fn negotiate<'a>( + &self, + supported: impl Iterator, + ) -> Option { + // 1. If no Accept-Encoding field is in the request, any content-coding is considered + // acceptable by the user agent. + + let supported_set = supported.collect::>(); + + if supported_set.is_empty() { + return None; + } + + if self.0.is_empty() { + // though it is not recommended to encode in this case, return identity encoding + return Some(Encoding::identity()); + } + + // 2. If the representation has no content-coding, then it is acceptable by default unless + // specifically excluded by the Accept-Encoding field stating either "identity;q=0" or + // "*;q=0" without a more specific entry for "identity". + + let acceptable_items = self.ranked_items().collect::>(); + + let identity_acceptable = is_identity_acceptable(&acceptable_items); + let identity_supported = supported_set.contains(&Encoding::identity()); + + if identity_acceptable && identity_supported && supported_set.len() == 1 { + return Some(Encoding::identity()); + } + + // 3. If the representation's content-coding is one of the content-codings listed in the + // Accept-Encoding field, then it is acceptable unless it is accompanied by a qvalue of 0. + + // 4. If multiple content-codings are acceptable, then the acceptable content-coding with + // the highest non-zero qvalue is preferred. + + let matched = acceptable_items + .into_iter() + .filter(|q| q.quality > Quality::ZERO) + // search relies on item list being in descending order of quality + .find(|q| { + let enc = &q.item; + matches!(enc, Preference::Specific(enc) if supported_set.contains(enc)) + }) + .map(|q| q.item); + + match matched { + Some(Preference::Specific(enc)) => Some(enc), + + _ if identity_acceptable => Some(Encoding::identity()), + + _ => None, + } + } + + /// Extracts the most preferable encoding, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first encoding is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// As per the spec, returns [`Preference::Any`] if acceptable list is empty. Though, if this is + /// returned, it is recommended to use an un-encoded representation. + /// + /// If `None` is returned, it means that the client has signalled that no representations + /// are acceptable. This should never occur for a well behaved user-agent. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn preference(&self) -> Option> { + // empty header indicates no preference + if self.0.is_empty() { + return Some(Preference::Any); + } + + let mut max_item = None; + let mut max_pref = Quality::ZERO; + + // uses manual max lookup loop since we want the first occurrence in the case of same + // preference but `Iterator::max_by_key` would give us the last occurrence + + for pref in &self.0 { + // only change if strictly greater + // equal items, even while unsorted, still have higher preference if they appear first + if pref.quality > max_pref { + max_pref = pref.quality; + max_item = Some(pref.item.clone()); + } + } + + // Return max_item if any items were above 0 quality... + max_item.or_else(|| { + // ...or else check for "*" or "identity". We can elide quality checks since + // entering this block means all items had "q=0". + match self.0.iter().find(|pref| { + matches!( + pref.item, + Preference::Any + | Preference::Specific(Encoding::Known(ContentEncoding::Identity)) + ) + }) { + // "identity" or "*" found so no representation is acceptable + Some(_) => None, + + // implicit "identity" is acceptable + None => Some(Preference::Specific(Encoding::identity())), + } + }) + } + + /// Returns a sorted list of encodings from highest to lowest precedence, accounting + /// for [q-factor weighting]. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn ranked(&self) -> Vec> { + self.ranked_items().map(|q| q.item).collect() + } + + fn ranked_items(&self) -> impl Iterator>> { + if self.0.is_empty() { + return vec![].into_iter(); + } + + let mut types = self.0.clone(); + + // use stable sort so items with equal q-factor retain listed order + types.sort_by(|a, b| { + // sort by q-factor descending + b.quality.cmp(&a.quality) + }); + + types.into_iter() + } +} + +/// Returns true if "identity" is an acceptable encoding. +/// +/// Internal algorithm relies on item list being in descending order of quality. +fn is_identity_acceptable(items: &'_ [QualityItem>]) -> bool { + if items.is_empty() { + return true; + } + + // Loop algorithm depends on items being sorted in descending order of quality. As such, it + // is sufficient to return (q > 0) when reaching either an "identity" or "*" item. + for q in items { + match (q.quality, &q.item) { + // occurrence of "identity;q=n"; return true if quality is non-zero + (q, Preference::Specific(Encoding::Known(ContentEncoding::Identity))) => { + return q > Quality::ZERO + } + + // occurrence of "*;q=n"; return true if quality is non-zero + (q, Preference::Any) => return q > Quality::ZERO, + + _ => {} + } + } + + // implicit acceptable identity + true +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header::*; + + macro_rules! accept_encoding { + () => { AcceptEncoding(vec![]) }; + ($($q:expr),+ $(,)?) => { AcceptEncoding(vec![$($q.parse().unwrap()),+]) }; + } + + /// Parses an encoding string. + fn enc(enc: &str) -> Preference { + enc.parse().unwrap() + } + + #[test] + fn detect_identity_acceptable() { + macro_rules! accept_encoding_ranked { + () => { accept_encoding!().ranked_items().collect::>() }; + ($($q:expr),+ $(,)?) => { accept_encoding!($($q),+).ranked_items().collect::>() }; + } + + let test = accept_encoding_ranked!(); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip"); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "br"); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "*;q=0.1"); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "identity;q=0.1"); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "identity;q=0.1", "*;q=0"); + assert!(is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0.1"); + assert!(is_identity_acceptable(&test)); + + let test = accept_encoding_ranked!("gzip", "*;q=0"); + assert!(!is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "identity;q=0"); + assert!(!is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "identity;q=0", "*;q=0"); + assert!(!is_identity_acceptable(&test)); + let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0"); + assert!(!is_identity_acceptable(&test)); + } + + #[test] + fn encoding_negotiation() { + // no preference + let test = accept_encoding!(); + assert_eq!(test.negotiate([].iter()), None); + + let test = accept_encoding!(); + assert_eq!( + test.negotiate([Encoding::identity()].iter()), + Some(Encoding::identity()), + ); + + let test = accept_encoding!("identity;q=0"); + assert_eq!(test.negotiate([Encoding::identity()].iter()), None); + + let test = accept_encoding!("*;q=0"); + assert_eq!(test.negotiate([Encoding::identity()].iter()), None); + + let test = accept_encoding!(); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::identity()), + ); + + let test = accept_encoding!("gzip"); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()), + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::identity()].iter()), + Some(Encoding::identity()), + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()), + ); + + let test = accept_encoding!("gzip", "identity;q=0"); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()), + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::identity()].iter()), + None + ); + + let test = accept_encoding!("gzip", "*;q=0"); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()), + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::identity()].iter()), + None + ); + + let test = accept_encoding!("gzip", "deflate", "br"); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()), + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::identity()].iter()), + Some(Encoding::brotli()) + ); + assert_eq!( + test.negotiate([Encoding::deflate(), Encoding::identity()].iter()), + Some(Encoding::deflate()) + ); + assert_eq!( + test.negotiate( + [Encoding::gzip(), Encoding::deflate(), Encoding::identity()].iter() + ), + Some(Encoding::gzip()) + ); + assert_eq!( + test.negotiate([Encoding::gzip(), Encoding::brotli(), Encoding::identity()].iter()), + Some(Encoding::gzip()) + ); + assert_eq!( + test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()), + Some(Encoding::gzip()) + ); + } + + #[test] + fn ranking_precedence() { + let test = accept_encoding!(); + assert!(test.ranked().is_empty()); + + let test = accept_encoding!("gzip"); + assert_eq!(test.ranked(), vec![enc("gzip")]); + + let test = accept_encoding!("gzip;q=0.900", "*;q=0.700", "br;q=1.0"); + assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]); + + let test = accept_encoding!("br", "gzip", "*"); + assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]); + } + + #[test] + fn preference_selection() { + assert_eq!(accept_encoding!().preference(), Some(Preference::Any)); + + assert_eq!(accept_encoding!("identity;q=0").preference(), None); + assert_eq!(accept_encoding!("*;q=0").preference(), None); + assert_eq!(accept_encoding!("compress;q=0", "*;q=0").preference(), None); + assert_eq!(accept_encoding!("identity;q=0", "*;q=0").preference(), None); + + let test = accept_encoding!("*;q=0.5"); + assert_eq!(test.preference().unwrap(), enc("*")); + + let test = accept_encoding!("br;q=0"); + assert_eq!(test.preference().unwrap(), enc("identity")); + + let test = accept_encoding!("br;q=0.900", "gzip;q=1.0", "*;q=0.500"); + assert_eq!(test.preference().unwrap(), enc("gzip")); + + let test = accept_encoding!("br", "gzip", "*"); + assert_eq!(test.preference().unwrap(), enc("br")); + } +} diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 011257b87..9943e121f 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -37,7 +37,7 @@ common_header! { /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// QualityItem::max("en-US".parse().unwrap()) + /// "en-US".parse().unwrap(), /// ]) /// ); /// ``` @@ -49,9 +49,9 @@ common_header! { /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ - /// QualityItem::max("da".parse().unwrap()), - /// QualityItem::new("en-GB".parse().unwrap(), q(0.8)), - /// QualityItem::new("en".parse().unwrap(), q(0.7)), + /// "da".parse().unwrap(), + /// "en-GB;q=0.8".parse().unwrap(), + /// "en;q=0.7".parse().unwrap(), /// ]) /// ); /// ``` @@ -93,6 +93,33 @@ common_header! { } impl AcceptLanguage { + /// Extracts the most preferable language, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first language is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// As per the spec, returns [`Preference::Any`] if contained list is empty. + /// + /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + pub fn preference(&self) -> Preference { + let mut max_item = None; + let mut max_pref = Quality::ZERO; + + // uses manual max lookup loop since we want the first occurrence in the case of same + // preference but `Iterator::max_by_key` would give us the last occurrence + + for pref in &self.0 { + // only change if strictly greater + // equal items, even while unsorted, still have higher preference if they appear first + if pref.quality > max_pref { + max_pref = pref.quality; + max_item = Some(pref.item.clone()); + } + } + + max_item.unwrap_or(Preference::Any) + } + /// Returns a sorted list of languages from highest to lowest precedence, accounting /// for [q-factor weighting]. /// @@ -112,33 +139,6 @@ impl AcceptLanguage { types.into_iter().map(|qitem| qitem.item).collect() } - - /// Extracts the most preferable language, accounting for [q-factor weighting]. - /// - /// If no q-factors are provided, the first language is chosen. Note that items without - /// q-factors are given the maximum preference value. - /// - /// As per the spec, returns [`Preference::Any`] if contained list is empty. - /// - /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 - pub fn preference(&self) -> Preference { - let mut max_item = None; - let mut max_pref = Quality::MIN; - - // uses manual max lookup loop since we want the first occurrence in the case of same - // preference but `Iterator::max_by_key` would give us the last occurrence - - for pref in &self.0 { - // only change if strictly greater - // equal items, even while unsorted, still have higher preference if they appear first - if pref.quality > max_pref { - max_pref = pref.quality; - max_item = Some(pref.item.clone()); - } - } - - max_item.unwrap_or(Preference::Any) - } } #[cfg(test)] @@ -152,7 +152,7 @@ mod tests { assert!(test.ranked().is_empty()); let test = AcceptLanguage(vec![QualityItem::max("fr-CH".parse().unwrap())]); - assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap())); + assert_eq!(test.ranked(), vec!["fr-CH".parse().unwrap()]); let test = AcceptLanguage(vec![ QualityItem::new("fr".parse().unwrap(), q(0.900)), diff --git a/src/http/header/content_disposition.rs b/src/http/header/content_disposition.rs index 26a9d8e76..8b7101aa1 100644 --- a/src/http/header/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -301,7 +301,6 @@ impl DispositionParam { /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. /// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). -// TODO: think about using private fields and smallvec #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { /// The disposition type diff --git a/src/http/header/encoding.rs b/src/http/header/encoding.rs index a61edda67..ab11f50be 100644 --- a/src/http/header/encoding.rs +++ b/src/http/header/encoding.rs @@ -1,69 +1,55 @@ use std::{fmt, str}; -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, Zstd, -}; +use actix_http::ContentEncoding; -/// A value to represent an encoding used in `Transfer-Encoding` or `Accept-Encoding` header. -#[derive(Debug, Clone, PartialEq, Eq)] +/// A value to represent an encoding used in the `Accept-Encoding` and `Content-Encoding` header. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Encoding { - /// The `chunked` encoding. - Chunked, + /// A supported content encoding. See [`ContentEncoding`] for variants. + Known(ContentEncoding), - /// The `br` encoding. - Brotli, + /// Some other encoding that is less common, can be any string. + Unknown(String), +} - /// The `gzip` encoding. - Gzip, +impl Encoding { + pub const fn identity() -> Self { + Self::Known(ContentEncoding::Identity) + } - /// The `deflate` encoding. - Deflate, + pub const fn brotli() -> Self { + Self::Known(ContentEncoding::Brotli) + } - /// The `compress` encoding. - Compress, + pub const fn deflate() -> Self { + Self::Known(ContentEncoding::Deflate) + } - /// The `identity` encoding. - Identity, + pub const fn gzip() -> Self { + Self::Known(ContentEncoding::Gzip) + } - /// The `trailers` encoding. - Trailers, - - /// The `zstd` encoding. - Zstd, - - /// Some other encoding that is less common, can be any String. - EncodingExt(String), + pub const fn zstd() -> Self { + Self::Known(ContentEncoding::Zstd) + } } impl fmt::Display for Encoding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - Zstd => "zstd", - EncodingExt(ref s) => s.as_ref(), + f.write_str(match self { + Encoding::Known(enc) => enc.as_str(), + Encoding::Unknown(enc) => enc.as_str(), }) } } impl str::FromStr for Encoding { type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - "zstd" => Ok(Zstd), - _ => Ok(EncodingExt(s.to_owned())), + + fn from_str(enc: &str) -> Result { + match enc.parse::() { + Ok(enc) => Ok(Self::Known(enc)), + Err(_) => Ok(Self::Unknown(enc.to_owned())), } } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d3cdf5763..3a0d5630d 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,20 +1,13 @@ //! For middleware documentation, see [`Compress`]. use std::{ - cmp, - convert::TryFrom as _, future::Future, marker::PhantomData, pin::Pin, task::{Context, Poll}, }; -use actix_http::{ - body::{EitherBody, MessageBody}, - encoding::Encoder, - header::{ContentEncoding, ACCEPT_ENCODING}, - StatusCode, -}; +use actix_http::encoding::Encoder; use actix_service::{Service, Transform}; use actix_utils::future::{ok, Either, Ready}; use futures_core::ready; @@ -22,9 +15,14 @@ use once_cell::sync::Lazy; use pin_project_lite::pin_project; use crate::{ - dev::BodyEncoding, + body::{EitherBody, MessageBody}, + dev::BodyEncoding as _, + http::{ + header::{self, AcceptEncoding, Encoding, HeaderValue}, + StatusCode, + }, service::{ServiceRequest, ServiceResponse}, - Error, HttpResponse, + Error, HttpMessage, HttpResponse, }; /// Middleware for compressing response payloads. @@ -40,21 +38,9 @@ use crate::{ /// .wrap(middleware::Compress::default()) /// .default_service(web::to(|| HttpResponse::NotFound())); /// ``` -#[derive(Debug, Clone)] -pub struct Compress(ContentEncoding); - -impl Compress { - /// Create new `Compress` middleware with the specified encoding. - pub fn new(encoding: ContentEncoding) -> Self { - Compress(encoding) - } -} - -impl Default for Compress { - fn default() -> Self { - Compress::new(ContentEncoding::Auto) - } -} +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct Compress; impl Transform for Compress where @@ -68,44 +54,14 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(CompressMiddleware { - service, - encoding: self.0, - }) + ok(CompressMiddleware { service }) } } pub struct CompressMiddleware { service: S, - encoding: ContentEncoding, } -static SUPPORTED_ALGORITHM_NAMES: Lazy = Lazy::new(|| { - #[allow(unused_mut)] // only unused when no compress features enabled - let mut encoding: Vec<&str> = vec![]; - - #[cfg(feature = "compress-brotli")] - { - encoding.push("br"); - } - - #[cfg(feature = "compress-gzip")] - { - encoding.push("gzip"); - encoding.push("deflate"); - } - - #[cfg(feature = "compress-zstd")] - encoding.push("zstd"); - - assert!( - !encoding.is_empty(), - "encoding can not be empty unless __compress feature has been explicitly enabled by itself" - ); - - encoding.join(", ") -}); - impl Service for CompressMiddleware where S: Service, Error = Error>, @@ -121,39 +77,43 @@ where #[allow(clippy::borrow_interior_mutable_const)] fn call(&self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding - let encoding_result = req - .headers() - .get(&ACCEPT_ENCODING) - .and_then(|val| val.to_str().ok()) - .map(|enc| AcceptEncoding::try_parse(enc, self.encoding)); + let accept_encoding = req.get_header::(); - match encoding_result { - // Missing header => fallback to identity - None => Either::left(CompressResponse { - encoding: ContentEncoding::Identity, - fut: self.service.call(req), - _phantom: PhantomData, - }), + let accept_encoding = match accept_encoding { + // missing header; fallback to identity + None => { + return Either::left(CompressResponse { + encoding: Encoding::identity(), + fut: self.service.call(req), + _phantom: PhantomData, + }) + } - // Valid encoding - Some(Ok(encoding)) => Either::left(CompressResponse { - encoding, - fut: self.service.call(req), - _phantom: PhantomData, - }), + // valid accept-encoding header + Some(accept_encoding) => accept_encoding, + }; - // There is an HTTP header but we cannot match what client as asked for - Some(Err(_)) => { - let res = HttpResponse::with_body( + match accept_encoding.negotiate(SUPPORTED_ENCODINGS.iter()) { + None => { + let mut res = HttpResponse::with_body( StatusCode::NOT_ACCEPTABLE, - SUPPORTED_ALGORITHM_NAMES.clone(), + SUPPORTED_ENCODINGS_STRING.as_str(), ); + res.headers_mut() + .insert(header::VARY, HeaderValue::from_static("Accept-Encoding")); + Either::right(ok(req .into_response(res) .map_into_boxed_body() .map_into_right_body())) } + + Some(encoding) => Either::left(CompressResponse { + fut: self.service.call(req), + encoding, + _phantom: PhantomData, + }), } } } @@ -165,7 +125,7 @@ pin_project! { { #[pin] fut: S::Future, - encoding: ContentEncoding, + encoding: Encoding, _phantom: PhantomData, } } @@ -182,10 +142,15 @@ where match ready!(this.fut.poll(cx)) { Ok(resp) => { - let enc = if let Some(enc) = resp.response().get_encoding() { + let enc = if let Some(enc) = resp.response().preferred_encoding() { enc } else { - *this.encoding + match this.encoding { + Encoding::Known(enc) => *enc, + Encoding::Unknown(enc) => { + unimplemented!("encoding {} should not be here", enc); + } + } }; Poll::Ready(Ok(resp.map_body(move |head, body| { @@ -198,178 +163,57 @@ where } } -struct AcceptEncoding { - encoding: ContentEncoding, - // TODO: use Quality or QualityItem - quality: f64, -} +static SUPPORTED_ENCODINGS_STRING: Lazy = Lazy::new(|| { + #[allow(unused_mut)] // only unused when no compress features enabled + let mut encoding: Vec<&str> = vec![]; -impl Eq for AcceptEncoding {} - -impl Ord for AcceptEncoding { - #[allow(clippy::comparison_chain)] - fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { - if self.quality > other.quality { - cmp::Ordering::Less - } else if self.quality < other.quality { - cmp::Ordering::Greater - } else { - cmp::Ordering::Equal - } - } -} - -impl PartialOrd for AcceptEncoding { - fn partial_cmp(&self, other: &AcceptEncoding) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for AcceptEncoding { - fn eq(&self, other: &AcceptEncoding) -> bool { - self.encoding == other.encoding && self.quality == other.quality - } -} - -/// Parse q-factor from quality strings. -/// -/// If parse fail, then fallback to default value which is 1. -/// More details available here: -fn parse_quality(parts: &[&str]) -> f64 { - for part in parts { - if part.trim().starts_with("q=") { - return part[2..].parse().unwrap_or(1.0); - } + #[cfg(feature = "compress-brotli")] + { + encoding.push("br"); } - 1.0 -} - -#[derive(Debug, PartialEq, Eq)] -enum AcceptEncodingError { - /// This error occurs when client only support compressed response and server do not have any - /// algorithm that match client accepted algorithms. - CompressionAlgorithmMismatch, -} - -impl AcceptEncoding { - fn new(tag: &str) -> Option { - let parts: Vec<&str> = tag.split(';').collect(); - let encoding = match parts.len() { - 0 => return None, - _ => match ContentEncoding::try_from(parts[0]) { - Err(_) => return None, - Ok(x) => x, - }, - }; - - let quality = parse_quality(&parts[1..]); - if quality <= 0.0 || quality > 1.0 { - return None; - } - - Some(AcceptEncoding { encoding, quality }) + #[cfg(feature = "compress-gzip")] + { + encoding.push("gzip"); + encoding.push("deflate"); } - /// Parse a raw Accept-Encoding header value into an ordered list then return the best match - /// based on middleware configuration. - pub fn try_parse( - raw: &str, - encoding: ContentEncoding, - ) -> Result { - let mut encodings = raw - .replace(' ', "") - .split(',') - .filter_map(AcceptEncoding::new) - .collect::>(); - - encodings.sort(); - - for enc in encodings { - if encoding == ContentEncoding::Auto || encoding == enc.encoding { - return Ok(enc.encoding); - } - } - - // Special case if user cannot accept uncompressed data. - // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding - // TODO: account for whitespace - if raw.contains("*;q=0") || raw.contains("identity;q=0") { - return Err(AcceptEncodingError::CompressionAlgorithmMismatch); - } - - Ok(ContentEncoding::Identity) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - macro_rules! assert_parse_eq { - ($raw:expr, $result:expr) => { - assert_eq!( - AcceptEncoding::try_parse($raw, ContentEncoding::Auto), - Ok($result) - ); - }; + #[cfg(feature = "compress-zstd")] + { + encoding.push("zstd"); } - macro_rules! assert_parse_fail { - ($raw:expr) => { - assert!(AcceptEncoding::try_parse($raw, ContentEncoding::Auto).is_err()); - }; + assert!( + !encoding.is_empty(), + "encoding can not be empty unless __compress feature has been explicitly enabled by itself" + ); + + encoding.join(", ") +}); + +static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { + let mut encodings = vec![Encoding::identity()]; + + #[cfg(feature = "compress-brotli")] + { + encodings.push(Encoding::brotli()); } - #[test] - fn test_parse_encoding() { - // Test simple case - assert_parse_eq!("br", ContentEncoding::Br); - assert_parse_eq!("gzip", ContentEncoding::Gzip); - assert_parse_eq!("deflate", ContentEncoding::Deflate); - assert_parse_eq!("zstd", ContentEncoding::Zstd); - - // Test space, trim, missing values - assert_parse_eq!("br,,,,", ContentEncoding::Br); - assert_parse_eq!("gzip , br, zstd", ContentEncoding::Gzip); - - // Test float number parsing - assert_parse_eq!("br;q=1 ,", ContentEncoding::Br); - assert_parse_eq!("br;q=1.0 , br", ContentEncoding::Br); - - // Test wildcard - assert_parse_eq!("*", ContentEncoding::Identity); - assert_parse_eq!("*;q=1.0", ContentEncoding::Identity); + #[cfg(feature = "compress-gzip")] + { + encodings.push(Encoding::gzip()); + encodings.push(Encoding::deflate()); } - #[test] - fn test_parse_encoding_qfactor_ordering() { - assert_parse_eq!("gzip, br, zstd", ContentEncoding::Gzip); - assert_parse_eq!("zstd, br, gzip", ContentEncoding::Zstd); - - assert_parse_eq!("gzip;q=0.4, br;q=0.6", ContentEncoding::Br); - assert_parse_eq!("gzip;q=0.8, br;q=0.4", ContentEncoding::Gzip); + #[cfg(feature = "compress-zstd")] + { + encodings.push(Encoding::zstd()); } - #[test] - fn test_parse_encoding_qfactor_invalid() { - // Out of range - assert_parse_eq!("gzip;q=-5.0", ContentEncoding::Identity); - assert_parse_eq!("gzip;q=5.0", ContentEncoding::Identity); + assert!( + !encodings.is_empty(), + "encodings can not be empty unless __compress feature has been explicitly enabled by itself" + ); - // Disabled - assert_parse_eq!("gzip;q=0", ContentEncoding::Identity); - } - - #[test] - fn test_parse_compression_required() { - // Check we fallback to identity if there is an unsupported compression algorithm - assert_parse_eq!("compress", ContentEncoding::Identity); - - // User do not want any compression - assert_parse_fail!("compress, identity;q=0"); - assert_parse_fail!("compress, identity;q=0.0"); - assert_parse_fail!("compress, *;q=0"); - assert_parse_fail!("compress, *;q=0.0"); - } -} + encodings +}); diff --git a/src/types/mod.rs b/src/types/mod.rs index 461d771eb..bab7c3bc0 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,19 +1,18 @@ //! Common extractors and responders. -// TODO: review visibility mod either; -pub(crate) mod form; +mod form; mod header; -pub(crate) mod json; +mod json; mod path; -pub(crate) mod payload; +mod payload; mod query; -pub(crate) mod readlines; +mod readlines; -pub use self::either::{Either, EitherExtractError}; -pub use self::form::{Form, FormConfig}; +pub use self::either::Either; +pub use self::form::{Form, FormConfig, UrlEncoded}; pub use self::header::Header; -pub use self::json::{Json, JsonConfig}; +pub use self::json::{Json, JsonBody, JsonConfig}; pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::{Query, QueryConfig}; diff --git a/src/types/payload.rs b/src/types/payload.rs index 73987def5..d2ab29639 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -248,6 +248,7 @@ impl PayloadConfig { } } } + Ok(()) } diff --git a/src/web.rs b/src/web.rs index e4339352b..4858600af 100644 --- a/src/web.rs +++ b/src/web.rs @@ -2,13 +2,12 @@ use std::future::Future; -use actix_http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::{ - error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, - route::Route, scope::Scope, service::WebService, Responder, + error::BlockingError, http::Method, service::WebService, FromRequest, Handler, Resource, + Responder, Route, Scope, }; pub use crate::config::ServiceConfig; diff --git a/tests/compression.rs b/tests/compression.rs new file mode 100644 index 000000000..c0bf10e4c --- /dev/null +++ b/tests/compression.rs @@ -0,0 +1,313 @@ +use actix_http::ContentEncoding; +use actix_web::{ + dev::BodyEncoding as _, + http::{header, StatusCode}, + middleware::Compress, + web, App, HttpResponse, +}; +use bytes::Bytes; + +mod test_utils; +use test_utils::{brotli, gzip, zstd}; + +static LOREM: &[u8] = include_bytes!("fixtures/lorem.txt"); +static LOREM_GZIP: &[u8] = include_bytes!("fixtures/lorem.txt.gz"); +static LOREM_BR: &[u8] = include_bytes!("fixtures/lorem.txt.br"); +static LOREM_ZSTD: &[u8] = include_bytes!("fixtures/lorem.txt.zst"); +static LOREM_XZ: &[u8] = include_bytes!("fixtures/lorem.txt.xz"); + +macro_rules! test_server { + () => { + actix_test::start(|| { + App::new() + .wrap(Compress::default()) + .route("/static", web::to(|| HttpResponse::Ok().body(LOREM))) + .route( + "/static-gzip", + web::to(|| { + HttpResponse::Ok() + // signal to compressor that content should not be altered + .encode_with(ContentEncoding::Identity) + // signal to client that content is encoded + .insert_header(ContentEncoding::Gzip) + .body(LOREM_GZIP) + }), + ) + .route( + "/static-br", + web::to(|| { + HttpResponse::Ok() + // signal to compressor that content should not be altered + .encode_with(ContentEncoding::Identity) + // signal to client that content is encoded + .insert_header(ContentEncoding::Brotli) + .body(LOREM_BR) + }), + ) + .route( + "/static-zstd", + web::to(|| { + HttpResponse::Ok() + // signal to compressor that content should not be altered + .encode_with(ContentEncoding::Identity) + // signal to client that content is encoded + .insert_header(ContentEncoding::Zstd) + .body(LOREM_ZSTD) + }), + ) + .route( + "/static-xz", + web::to(|| { + HttpResponse::Ok() + // signal to compressor that content should not be altered + .encode_with(ContentEncoding::Identity) + // signal to client that content is encoded as 7zip + .insert_header((header::CONTENT_ENCODING, "xz")) + .body(LOREM_XZ) + }), + ) + .route( + "/echo", + web::to(|body: Bytes| HttpResponse::Ok().body(body)), + ) + }) + }; +} + +#[actix_rt::test] +async fn negotiate_encoding_identity() { + let srv = test_server!(); + + let req = srv + .post("/static") + .insert_header((header::ACCEPT_ENCODING, "identity")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING), None); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + srv.stop().await; +} + +#[actix_rt::test] +async fn negotiate_encoding_gzip() { + let srv = test_server!(); + + let req = srv + .post("/static") + .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + let mut res = srv + .post("/static") + .no_decompress() + .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd")) + .send() + .await + .unwrap(); + let bytes = res.body().await.unwrap(); + assert_eq!(gzip::decode(bytes), LOREM); + + srv.stop().await; +} + +#[actix_rt::test] +async fn negotiate_encoding_br() { + let srv = test_server!(); + + let req = srv + .post("/static") + .insert_header((header::ACCEPT_ENCODING, "br,zstd,gzip")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "br"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + let mut res = srv + .post("/static") + .no_decompress() + .insert_header((header::ACCEPT_ENCODING, "br,zstd,gzip")) + .send() + .await + .unwrap(); + let bytes = res.body().await.unwrap(); + assert_eq!(brotli::decode(bytes), LOREM); + + srv.stop().await; +} + +#[actix_rt::test] +async fn negotiate_encoding_zstd() { + let srv = test_server!(); + + let req = srv + .post("/static") + .insert_header((header::ACCEPT_ENCODING, "zstd,gzip,br")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "zstd"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + let mut res = srv + .post("/static") + .no_decompress() + .insert_header((header::ACCEPT_ENCODING, "zstd,gzip,br")) + .send() + .await + .unwrap(); + let bytes = res.body().await.unwrap(); + assert_eq!(zstd::decode(bytes), LOREM); + + srv.stop().await; +} + +#[cfg(all( + feature = "compress-brotli", + feature = "compress-gzip", + feature = "compress-zstd", +))] +#[actix_rt::test] +async fn client_encoding_prefers_brotli() { + let srv = test_server!(); + + let req = srv.post("/static").send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "br"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + srv.stop().await; +} + +#[actix_rt::test] +async fn gzip_no_decompress() { + let srv = test_server!(); + + let req = srv + .post("/static-gzip") + // don't decompress response body + .no_decompress() + // signal that we want a compressed body + .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM_GZIP)); + + srv.stop().await; +} + +#[actix_rt::test] +async fn manual_custom_coding() { + let srv = test_server!(); + + let req = srv + .post("/static-xz") + // don't decompress response body + .no_decompress() + // signal that we want a compressed body + .insert_header((header::ACCEPT_ENCODING, "xz")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "xz"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM_XZ)); + + srv.stop().await; +} + +#[actix_rt::test] +async fn deny_identity_coding() { + let srv = test_server!(); + + let req = srv + .post("/static") + // signal that we want a compressed body + .insert_header((header::ACCEPT_ENCODING, "br, identity;q=0")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "br"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM)); + + srv.stop().await; +} + +#[actix_rt::test] +async fn deny_identity_coding_no_decompress() { + let srv = test_server!(); + + let req = srv + .post("/static-br") + // don't decompress response body + .no_decompress() + // signal that we want a compressed body + .insert_header((header::ACCEPT_ENCODING, "br, identity;q=0")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "br"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM_BR)); + + srv.stop().await; +} + +// TODO: fix test +// currently fails because negotiation doesn't consider unknown encoding types +#[ignore] +#[actix_rt::test] +async fn deny_identity_for_manual_coding() { + let srv = test_server!(); + + let req = srv + .post("/static-xz") + // don't decompress response body + .no_decompress() + // signal that we want a compressed body + .insert_header((header::ACCEPT_ENCODING, "xz, identity;q=0")) + .send(); + + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "xz"); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(LOREM_XZ)); + + srv.stop().await; +} diff --git a/tests/fixtures/lorem.txt b/tests/fixtures/lorem.txt new file mode 100644 index 000000000..a2abb6432 --- /dev/null +++ b/tests/fixtures/lorem.txt @@ -0,0 +1,5 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin interdum tincidunt lacus, sed tempor lorem consectetur et. Pellentesque et egestas sem, at cursus massa. Nunc feugiat elit sit amet ipsum commodo luctus. Proin auctor dignissim pharetra. Integer iaculis quam a tellus auctor, vitae auctor nisl viverra. Nullam consequat maximus porttitor. Pellentesque tortor enim, molestie at varius non, tempor non nibh. Suspendisse tempus erat lorem, vel faucibus magna blandit vel. Sed pellentesque ligula augue, vitae fermentum eros blandit et. Cras dignissim in massa ut varius. Vestibulum commodo nunc sit amet pellentesque dignissim. + +Donec imperdiet blandit lobortis. Suspendisse fringilla nunc quis venenatis tempor. Nunc tempor sed erat sed convallis. Pellentesque aliquet elit lectus, quis vulputate arcu pharetra sed. Etiam laoreet aliquet arcu cursus vehicula. Maecenas odio odio, elementum faucibus sollicitudin vitae, pellentesque ac purus. Donec venenatis faucibus lorem, et finibus lacus tincidunt vitae. Quisque laoreet metus sapien, vitae euismod mauris lobortis malesuada. Integer sit amet elementum turpis. Maecenas ex mauris, dapibus eu placerat vitae, rutrum convallis enim. Nulla vitae orci ultricies, sagittis turpis et, lacinia dui. Praesent egestas urna turpis, sit amet feugiat mauris tristique eu. Quisque id tempor libero. Donec ullamcorper dapibus lorem, vel consequat risus congue a. + +Nullam dignissim ut lectus vitae tempor. Pellentesque ut odio fringilla, volutpat mi et, vulputate tellus. Fusce eget diam non odio tincidunt viverra eu vel augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam sed eleifend purus, vitae aliquam orci. Cras fringilla justo eget tempus bibendum. Phasellus venenatis, odio nec pulvinar commodo, quam neque lacinia turpis, ut rutrum tortor massa eu nulla. Vivamus tincidunt ut lectus a gravida. Donec varius mi quis enim interdum ultrices. Sed aliquam consectetur nisi vitae viverra. Praesent nec ligula egestas, porta lectus sed, consectetur augue. diff --git a/tests/fixtures/lorem.txt.br b/tests/fixtures/lorem.txt.br new file mode 100644 index 0000000000000000000000000000000000000000..e12d7510ee184cbd234d803327ee4f17285eb5be GIT binary patch literal 905 zcmV;419tquKtKQ>rfUAzr>O?L;iCEFef5mQ_{~D193IB{^CrMb%JNqgxdcY!GuiB4Q*LyOU-gf=gDZa z13H*He)yu{FUh~(Ya(fly(}@LRcr|DP|4_WjB%ofX${h=V=S!raXd?*2|YAY0-fSXL{(T_o2SrYjaJguL#0Wb-#9r9^!2279s?dde4r3#dO|l zAR!C397=Hx7Vnw;c6+l-8#qsO%e29-FxGu}GCGic7{M6FkRfW%I!(a!rY6Z~9@wI9 z2oa@W{93j0GWsel0$CSFK#%7!*mXl1je=fu zgqr9{2?nPbFE{x`+m?SY8Pq(J&PZ%Kpw+ZCn%e^J>I)FDgiUSvsa~QNs`HAnlOZE* z*#T?+e!IDeQ(C^rh?)bxWA0WfDP_{wvEf>y@+>05J~$GqwXNLouA*rtdJ>((>@^Z8 z;)s|W!^63YS;Rt@wUuz*6{mADoWV#a_^1;z6BVhnI^*?G>WWr(4vJ6)L|-|nsCDg7 zKGZtT&3{>46`8SNNzM%B{;SAU68!O%JMU_}=8hjP5{7P4$!_&BVh7F|7fsx@vfCt3 zHPwakCy=CG$xQgT`rwEb>_WGTyksiP!FXD*BR&fT%2ms+PC5-!+)bvTXX@Ue0nr3H fZVdLv<|E@cj{%nl-80*ql$VUo3+=PJV4S`IH*m~F literal 0 HcmV?d00001 diff --git a/tests/fixtures/lorem.txt.gz b/tests/fixtures/lorem.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..d3a0664a320f93c575f0b77417152154140cefc5 GIT binary patch literal 920 zcmV;J184jniwFn>|IlFq18i?{Wo<5Wcys_=RZEWSHVoX)DR=;*FOX$EK!7Aj@PO>u zHrvBMmOT12-tkk^&)sh`)3zn5SXC_j5uzNVXDSD6!G%akGw~sLp*8pf zM^yYBXYUe8f!ArGufhj0Fgu51+s1{Snps8})Npw7lo%@{=eYNU%^6U?>w&%KIcLR& zADYA)t0G>};Sn7ARk2o|92NU~@XO!~zj56C^G4rFI>m2*FZCl9O2nqhDgcV32k_~= zlC^uLyJM`+_6gge!}*HB>0TV8rM<}L?;-KP0?;Tiq*=gZ`Z+?Ai$8E$6`?X<-{@a! z<5H;vU67TO%`^Ff8AA1%sxvmUffwSXRL{9nclH#H~KXz)OQRSuz3`z z3%hln@~Q9^tnw$z3Ydw)rlI~>aGZ3`r+g^@-m1B5pt&luymQAZoh2IQjk&A)S)aooMYewv;trisdpSpj@3$Ct~xM%+&ZohSEb@|1Y2TZ!@K5cf_fxbdZ8)^ zVA_gW8n7gwnkiEv2BocDF6<*+(BJ;xDQ zOS=HPw3%q$83qB>-IYDVTLyw5Z}7p6KKXc?vP(jyJ@Qj+So+kqxQ9kHWz62!MLPIu u$vv8Xy*%L&?cB_}X!_gg_k{0OU+R6t0}Y7&;byP;e*6!TUtO9B2mk=cqQn#c literal 0 HcmV?d00001 diff --git a/tests/fixtures/lorem.txt.xz b/tests/fixtures/lorem.txt.xz new file mode 100644 index 0000000000000000000000000000000000000000..bb45fe7535a6be83dfd723f7cef89a30bf91a8fe GIT binary patch literal 1020 zcmVvH<%0_2e@^`p>hS)*d z1vSlsrF;^l1>2ClFBH>pa+auyeS}tVjcOv8tDZf>RqxED!`*h!Yk|yv>94Zxt%Y>l zx!)*UoMfrNhCmDJ5z-pE^?*Khxa-Rkh5F9&sE&}>fzuKP9f!n_^287?_V%J<`&(N` z%EaV)8FkZ3m^+%RdW2gqzqwI}Q-@s)e7;FLTr8-qXl)82u5Hyax3CUJMg#$f!?{6e zGBqGhAn>3a8r!A2pCP9*NKi{NZ<;hPB&2^lHx8=1J!PW_M*Kw&UoK6hjjm#={;IW1 zr~sJjK%Y_b^=_4(;5Q+kFY@-ALsfu$YNagy9ARpbW;`4-`UsFa@D#G!9mcK&*wutK z3(km&0Jk^-G~}Be%LNxwy6^K`PpRgUE_x+3D6HC*EJDO;B7(0)U)%=bHQZYEY+6;I zB820Pp)Gz4ddfA|<_FHC0EGP$@bE~f)cpQg3{!iop(5NgPt{jUZEVbSg=rNEK>H}D zIp)Mv^j~cD=QMWp7piqRNER2Bp zzI+&u&w$(0wMCe36A*NKf{(uiZKzT{EclGTf3oq|ZGa*e<}tpst}3p&gT%#->^?DE zY~BJL`vq0E>H7W#OrO8WGSj6f+3!~V&YqHqviV3a-(<1gz28HGOyfzUQtT%k?T!noG7SCE zTqagZa=8zm#8ZuT_|B+W7qkPXZ`)VqUHNQAqNuXZq6U zjz!qrdmNv9e3Ys{EG(b|wF%cc*de0X=h*ESnMga0K7|S26hB_NHz>u?T5o(>T|4oC>mUaFFC`5PY5P z_Q{~AG~^+!`Xv0uZzrP@}x z^QzK5oQB4H^|Oz4>rnhswCs$vpjX0(ljl5ZvbFb4-n9oiNN1WPP9RSwLoT>X@CbSC zYIKgaCpfoE=H=AYtEu0ptuzi<&! literal 0 HcmV?d00001 diff --git a/tests/fixtures/lorem.txt.zst b/tests/fixtures/lorem.txt.zst new file mode 100644 index 0000000000000000000000000000000000000000..13bea6d7a26e41c8c2bc848dc4d7ec0e764eed2b GIT binary patch literal 898 zcmV-|1AY7`wJ-f-2?wPc07kQU9biq;2GAngv5Dk9#kP_V3OgqJdn{wnx>gdr1Uzp5 za{zGwhv^wOQK*LHfEYdVcib40maSYZdz?%{C>5u!*md62)c`18UO%Dl-oP2XmDbE&q9GrXhy4N2yJ0|Y`M-JKZuR#&DBx3 z?ohmJEqpAop@rsL%e*;5av7X(Q2cCMI_DlMv1{5%$OifuCdH&z#e_H)J))4c7NX8U zwB(6T{;c$gjofogT}HJK&YvxHqXjRR(4sDuI*8YhGBzSCkKn4QO*@60$1P-&P=4*~ zNU5Rkf)n?J)o>9P#KzsMzWkh&cFab)NzOVeRtQei*>VR<)C^M{qq-6}u?5SfrHh<3 za<$m`sr)&WTq?#guXT2oIKL2e&W}4T@`_23j@nHCWjuci;iEE73J({m)Kl{s77jsvW4i|gl#EjF~xgg8yjcjwU&F}+$dsvE17+yW<#=$@pz zW!ob+k7x*{lF$_YLR|BxMjh^1T78Y{cSFs2)gP5M(q4{w)&YR5sNs=ONX^UyNmEeB zbPx~$Aw)T+2OvtBvu)A=;|kqt*(622pA>V)D3A4`#+|OGKDq$i0EBx>XFM<+AkHCQ zSQhEV{!7ayBqNf8M8{>!6p+w=Z0H4=knDrU7AUsv-Wrtv^HmIHe6+fBg8^QWEpC*q z>mdf7%Ui3~|M=8+V!@|p5L%I!3-nqR!t4s<#78)j&8*R0LQb0`F-H$v5r12S8pCKJ zb=NYN=ieAP(;iKfU7^K{%L;VVG>SVVsDN}iH+>r4GfZ<58IAnmXiOxHfkcqEzBbtG znUus>V3JezR^va-&N4fpk!3iLiBGHAUbhFiH}$g*-f`DfVIj#KccO`v) -> Vec { + let mut encoder = GzEncoder::new(Vec::new(), Compression::fast()); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = GzDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod deflate { + use super::*; + use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::fast()); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = ZlibDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod brotli { + use super::*; + use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = BrotliEncoder::new(Vec::new(), 3); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = BrotliDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod zstd { + use super::*; + use ::zstd::stream::{read::Decoder, write::Encoder}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = Encoder::new(Vec::new(), 3).unwrap(); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = Decoder::new(bytes.as_ref()).unwrap(); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} From 25fe1bbaa5b3cc2c8e4629fb0866036cf4c778d4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 3 Jan 2022 14:05:08 +0000 Subject: [PATCH 244/861] add double compress layer test --- Cargo.toml | 1 + actix-http/src/encoding/encoder.rs | 12 +++--- src/middleware/compress.rs | 60 ++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7abcba5a6..0e966cf46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.17", features = ["openssl"] } brotli2 = "0.3.2" +const-str = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index f40e0e579..6f6fc09d8 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -56,16 +56,16 @@ impl Encoder { } pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { - let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING) - || head.status == StatusCode::SWITCHING_PROTOCOLS - || head.status == StatusCode::NO_CONTENT - || encoding == ContentEncoding::Identity); - // no need to compress an empty body if matches!(body.size(), BodySize::None) { return Self::none(); } + let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING) + || head.status == StatusCode::SWITCHING_PROTOCOLS + || head.status == StatusCode::NO_CONTENT + || encoding == ContentEncoding::Identity); + let body = match body.try_into_bytes() { Ok(body) => EncoderBody::Full { body }, Err(body) => EncoderBody::Stream { body }, @@ -301,7 +301,7 @@ impl ContentEncoder { Some(ContentEncoder::Zstd(encoder)) } - ContentEncoding::Identity => None, + _ => None, } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 3a0d5630d..056381847 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -217,3 +217,63 @@ static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { encodings }); + +// move cfg(feature) to prevents_double_compressing if more tests are added +#[cfg(feature = "compress-gzip")] +#[cfg(test)] +mod tests { + use super::*; + use crate::{middleware::DefaultHeaders, test, web, App}; + + pub fn gzip_decode(bytes: impl AsRef<[u8]>) -> Vec { + use std::io::Read as _; + let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } + + #[actix_rt::test] + async fn prevents_double_compressing() { + const D: &str = "hello world "; + const DATA: &str = const_str::repeat!(D, 100); + + let app = test::init_service({ + App::new() + .wrap(Compress::default()) + .route( + "/single", + web::get().to(move || HttpResponse::Ok().body(DATA)), + ) + .service( + web::resource("/double") + .wrap(Compress::default()) + .wrap(DefaultHeaders::new().add(("x-double", "true"))) + .route(web::get().to(move || HttpResponse::Ok().body(DATA))), + ) + }) + .await; + + let req = test::TestRequest::default() + .uri("/single") + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .to_request(); + let res = test::call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get("x-double"), None); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip"); + let bytes = test::read_body(res).await; + assert_eq!(gzip_decode(bytes), DATA.as_bytes()); + + let req = test::TestRequest::default() + .uri("/double") + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .to_request(); + let res = test::call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.headers().get("x-double").unwrap(), "true"); + assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip"); + let bytes = test::read_body(res).await; + assert_eq!(gzip_decode(bytes), DATA.as_bytes()); + } +} From 68cd853aa22aa8f9ffa204e15db2828ae6ca5049 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 3 Jan 2022 14:59:01 +0000 Subject: [PATCH 245/861] improve docs for Compress --- Cargo.toml | 11 ++++---- actix-http/src/encoding/encoder.rs | 5 ++-- src/dev.rs | 31 ---------------------- src/middleware/compress.rs | 42 +++++++++++++++++++++++++++--- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e966cf46..3118f3b03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,15 +28,15 @@ path = "src/lib.rs" resolver = "2" members = [ ".", - "awc", - "actix-http", "actix-files", + "actix-http-test", + "actix-http", "actix-multipart", + "actix-router", + "actix-test", "actix-web-actors", "actix-web-codegen", - "actix-http-test", - "actix-test", - "actix-router", + "awc", ] [features] @@ -105,6 +105,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] +actix-files = "0.6.0-beta.12" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.17", features = ["openssl"] } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 6f6fc09d8..2848ad697 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -73,7 +73,7 @@ impl Encoder { if should_encode { // wrap body only if encoder is feature-enabled - if let Some(enc) = ContentEncoder::encoder(encoding) { + if let Some(enc) = ContentEncoder::select(encoding) { update_head(encoding, head); return Encoder { @@ -168,6 +168,7 @@ where cx: &mut Context<'_>, ) -> Poll>> { let mut this = self.project(); + loop { if *this.eof { return Poll::Ready(None); @@ -276,7 +277,7 @@ enum ContentEncoder { } impl ContentEncoder { - fn encoder(encoding: ContentEncoding) -> Option { + fn select(encoding: ContentEncoding) -> Option { match encoding { #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( diff --git a/src/dev.rs b/src/dev.rs index b15d15747..333d1acf8 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -46,9 +46,6 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { } /// Helper trait for managing response encoding. -/// -/// Use `pre_encoded_with` to flag response as already encoded. For example, when serving a Gzip -/// compressed file from disk. pub trait BodyEncoding { /// Get content encoding fn preferred_encoding(&self) -> Option; @@ -59,21 +56,10 @@ pub trait BodyEncoding { /// /// [`Compress`]: crate::middleware::Compress fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self; - - // /// Flags that a file already is encoded so that [`Compress`] does not modify it. - // /// - // /// Effectively a shortcut for `compress_with("identity")` - // /// plus `insert_header(ContentEncoding, encoding)`. - // /// - // /// [`Compress`]: crate::middleware::Compress - // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self; } struct CompressWith(ContentEncoding); -// TODO: add or delete this -// struct PreCompressed(ContentEncoding); - impl BodyEncoding for crate::HttpResponseBuilder { fn preferred_encoding(&self) -> Option { self.extensions().get::().map(|enc| enc.0) @@ -83,11 +69,6 @@ impl BodyEncoding for crate::HttpResponseBuilder { self.extensions_mut().insert(CompressWith(encoding)); self } - - // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { - // self.extensions_mut().insert(PreCompressed(encoding)); - // self - // } } impl BodyEncoding for crate::HttpResponse { @@ -99,11 +80,6 @@ impl BodyEncoding for crate::HttpResponse { self.extensions_mut().insert(CompressWith(encoding)); self } - - // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { - // self.extensions_mut().insert(PreCompressed(encoding)); - // self - // } } impl BodyEncoding for ServiceResponse { @@ -120,13 +96,6 @@ impl BodyEncoding for ServiceResponse { .insert(CompressWith(encoding)); self } - - // fn pre_encoded_with(&mut self, encoding: ContentEncoding) -> &mut Self { - // self.request() - // .extensions_mut() - // .insert(PreCompressed(encoding)); - // self - // } } // TODO: remove these impls ? diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 056381847..058f3d43e 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -27,17 +27,51 @@ use crate::{ /// Middleware for compressing response payloads. /// -/// Use `BodyEncoding` trait for overriding response compression. To disable compression set -/// encoding to `ContentEncoding::Identity`. +/// # Encoding Negotiation +/// `Compress` will read the `Accept-Encoding` header to negotiate which compression codec to use. +/// Payloads are not compressed if the header is not sent. The `compress-*` [feature flags] are also +/// considered in this selection process. +/// +/// # Pre-compressed Payload +/// If you are serving some data is already using a compressed representation (e.g., a gzip +/// compressed HTML file from disk) you can signal this to `Compress` by setting an appropriate +/// `Content-Encoding` header. In addition to preventing double compressing the payload, this header +/// is required by the spec when using compressed representations and will inform the client that +/// the content should be uncompressed. +/// +/// However, it is not advised to unconditionally serve encoded representations of content because +/// the client may not support it. The [`AcceptEncoding`] typed header has some utilities to help +/// perform manual encoding negotiation, if required. When negotiating content encoding, it is also +/// required by the spec to send a `Vary: Accept-Encoding` header. +/// +/// A (naïve) example serving an pre-compressed Gzip file is included below. /// /// # Examples +/// To enable automatic payload compression just include `Compress` as a top-level middleware: /// ``` -/// use actix_web::{web, middleware, App, HttpResponse}; +/// use actix_web::{middleware, web, App, HttpResponse}; /// /// let app = App::new() /// .wrap(middleware::Compress::default()) -/// .default_service(web::to(|| HttpResponse::NotFound())); +/// .default_service(web::to(|| HttpResponse::Ok().body("hello world"))); /// ``` +/// +/// Pre-compressed Gzip file being served from disk with correct headers added to bypass middleware: +/// ```no_run +/// use actix_web::{middleware, http::header, web, App, HttpResponse, Responder}; +/// +/// async fn index_handler() -> actix_web::Result { +/// Ok(actix_files::NamedFile::open("./assets/index.html.gz")? +/// .customize() +/// .insert_header(header::ContentEncoding::Gzip)) +/// } +/// +/// let app = App::new() +/// .wrap(middleware::Compress::default()) +/// .default_service(web::to(index_handler)); +/// ``` +/// +/// [feature flags]: ../index.html#crate-features #[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct Compress; From 19a46e3925fb1b6f443a5923972870cf4fe9dd9a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 3 Jan 2022 15:35:47 +0000 Subject: [PATCH 246/861] fix doc test --- src/middleware/compress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 058f3d43e..bdd193693 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -61,7 +61,7 @@ use crate::{ /// use actix_web::{middleware, http::header, web, App, HttpResponse, Responder}; /// /// async fn index_handler() -> actix_web::Result { -/// Ok(actix_files::NamedFile::open("./assets/index.html.gz")? +/// Ok(actix_files::NamedFile::open_async("./assets/index.html.gz").await? /// .customize() /// .insert_header(header::ContentEncoding::Gzip)) /// } From 0bc4ae9158d51e06e4845bc2e0ba987a5e3aae2e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 3 Jan 2022 18:46:04 +0000 Subject: [PATCH 247/861] remove `BodyEncoding` trait (#2565) --- .github/workflows/clippy-fmt.yml | 11 +- CHANGES.md | 2 + actix-files/src/lib.rs | 3 +- actix-files/src/named.rs | 67 +++--- actix-http/CHANGES.md | 2 + actix-http/src/header/map.rs | 2 +- actix-http/src/header/mod.rs | 4 +- .../src/header/shared/content_encoding.rs | 1 + awc/Cargo.toml | 2 + awc/tests/test_client.rs | 151 ++++-------- tests/test_utils.rs => awc/tests/utils.rs | 0 src/dev.rs | 78 ------- src/http/header/entity.rs | 84 ++++--- src/http/header/etag.rs | 20 +- src/http/header/if_match.rs | 9 +- src/http/header/if_none_match.rs | 4 +- src/middleware/compress.rs | 13 +- tests/compression.rs | 14 +- tests/test_server.rs | 215 ++++++++---------- tests/utils.rs | 76 +++++++ 20 files changed, 355 insertions(+), 403 deletions(-) rename tests/test_utils.rs => awc/tests/utils.rs (100%) create mode 100644 tests/utils.rs diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 957256d32..9fcb0a561 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -14,6 +14,7 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: stable + profile: minimal components: rustfmt - name: Check with rustfmt uses: actions-rs/cargo@v1 @@ -30,10 +31,18 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: stable + profile: minimal components: clippy override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --workspace --all-features --tests + args: --workspace --tests --examples --all-features diff --git a/CHANGES.md b/CHANGES.md index 423ea9fdc..9e5acce5d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,8 +15,10 @@ ### Removed - `Compress::new`; restricting compression algorithm is done through feature flags. [#2501] +- `BodyEncoding` trait; signalling content encoding is now only done via the `Content-Encoding` header. [#2565] [#2501]: https://github.com/actix/actix-web/pull/2501 +[#2565]: https://github.com/actix/actix-web/pull/2565 ## 4.0.0-beta.18 - 2021-12-29 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 6408e02da..8ed7d44e0 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -597,7 +597,8 @@ mod tests { .to_request(); let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); - assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + assert!(res.headers().contains_key(header::CONTENT_ENCODING)); + assert!(!test::read_body(res).await.is_empty()); } #[actix_rt::test] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 04e453580..019730dc6 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -9,14 +9,11 @@ use std::{ use actix_service::{Service, ServiceFactory}; use actix_web::{ body::{self, BoxBody, SizedStream}, - dev::{ - AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest, - ServiceResponse, - }, + dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, http::{ header::{ self, Charset, ContentDisposition, ContentEncoding, DispositionParam, - DispositionType, ExtendedValue, + DispositionType, ExtendedValue, HeaderValue, }, StatusCode, }, @@ -224,7 +221,6 @@ impl NamedFile { }) } - #[cfg(not(feature = "experimental-io-uring"))] /// Attempts to open a file in read-only mode. /// /// # Examples @@ -232,6 +228,7 @@ impl NamedFile { /// use actix_files::NamedFile; /// let file = NamedFile::open("foo.txt"); /// ``` + #[cfg(not(feature = "experimental-io-uring"))] pub fn open>(path: P) -> io::Result { let file = File::open(&path)?; Self::from_file(file, path) @@ -295,23 +292,21 @@ impl NamedFile { self } - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. + /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred + /// from the filename extension. #[inline] pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { self.content_type = mime_type; self } - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. + /// Set the Content-Disposition for serving this file. This allows changing the + /// `inline/attachment` disposition as well as the filename sent to the peer. /// /// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and - /// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, - /// and the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using. - /// [`std::ffi::OsStr::to_string_lossy`] + /// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, and the + /// filename is taken from the path provided in the `open` method after converting it to UTF-8 + /// (using `to_string_lossy`). #[inline] pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { self.content_disposition = cd; @@ -337,7 +332,7 @@ impl NamedFile { self } - /// Specifies whether to use ETag or not. + /// Specifies whether to return `ETag` header in response. /// /// Default is true. #[inline] @@ -346,7 +341,7 @@ impl NamedFile { self } - /// Specifies whether to use Last-Modified or not. + /// Specifies whether to return `Last-Modified` header in response. /// /// Default is true. #[inline] @@ -364,7 +359,7 @@ impl NamedFile { self } - /// Creates a etag in a format is similar to Apache's. + /// Creates an `ETag` in a format is similar to Apache's. pub(crate) fn etag(&self) -> Option { self.modified.as_ref().map(|mtime| { let ino = { @@ -386,7 +381,7 @@ impl NamedFile { .duration_since(UNIX_EPOCH) .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( + header::EntityTag::new_strong(format!( "{:x}:{:x}:{:x}:{:x}", ino, self.md.len(), @@ -405,12 +400,13 @@ impl NamedFile { if self.status_code != StatusCode::OK { let mut res = HttpResponse::build(self.status_code); - if self.flags.contains(Flags::PREFER_UTF8) { - let ct = equiv_utf8_text(self.content_type.clone()); - res.insert_header((header::CONTENT_TYPE, ct.to_string())); + let ct = if self.flags.contains(Flags::PREFER_UTF8) { + equiv_utf8_text(self.content_type.clone()) } else { - res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); - } + self.content_type + }; + + res.insert_header((header::CONTENT_TYPE, ct.to_string())); if self.flags.contains(Flags::CONTENT_DISPOSITION) { res.insert_header(( @@ -420,7 +416,7 @@ impl NamedFile { } if let Some(current_encoding) = self.encoding { - res.encode_with(current_encoding); + res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str())); } let reader = chunked::new_chunked_read(self.md.len(), 0, self.file); @@ -478,12 +474,13 @@ impl NamedFile { let mut res = HttpResponse::build(self.status_code); - if self.flags.contains(Flags::PREFER_UTF8) { - let ct = equiv_utf8_text(self.content_type.clone()); - res.insert_header((header::CONTENT_TYPE, ct.to_string())); + let ct = if self.flags.contains(Flags::PREFER_UTF8) { + equiv_utf8_text(self.content_type.clone()) } else { - res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); - } + self.content_type + }; + + res.insert_header((header::CONTENT_TYPE, ct.to_string())); if self.flags.contains(Flags::CONTENT_DISPOSITION) { res.insert_header(( @@ -492,9 +489,8 @@ impl NamedFile { )); } - // default compressing if let Some(current_encoding) = self.encoding { - res.encode_with(current_encoding); + res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str())); } if let Some(lm) = last_modified { @@ -517,7 +513,12 @@ impl NamedFile { length = ranges[0].length; offset = ranges[0].start; - res.encode_with(ContentEncoding::Identity); + // don't allow compression middleware to modify partial content + res.insert_header(( + header::CONTENT_ENCODING, + HeaderValue::from_static("identity"), + )); + res.insert_header(( header::CONTENT_RANGE, format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cbdccd93c..621b42450 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -13,6 +13,7 @@ - `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501] - Rename `ContentEncoding::{Br => Brotli}`. [#2501] - Minimum supported Rust version (MSRV) is now 1.54. +- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565] ### Fixed - `ContentEncoding::Identity` can now be parsed from a string. [#2501] @@ -23,6 +24,7 @@ - `ContentEncoding::is_compression()`. [#2501] [#2501]: https://github.com/actix/actix-web/pull/2501 +[#2565]: https://github.com/actix/actix-web/pull/2565 ## 3.0.0-beta.17 - 2021-12-27 diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 5cf4ba2fa..33fb262c4 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -6,7 +6,7 @@ use ahash::AHashMap; use http::header::{HeaderName, HeaderValue}; use smallvec::{smallvec, SmallVec}; -use crate::header::AsHeaderName; +use super::AsHeaderName; /// A multi-map of HTTP headers. /// diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 4e9140db4..5f352fc12 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -50,10 +50,10 @@ pub use self::utils::{ /// An interface for types that already represent a valid header. pub trait Header: TryIntoHeaderValue { - /// Returns the name of the header field + /// Returns the name of the header field. fn name() -> HeaderName; - /// Parse a header + /// Parse the header from a HTTP message. fn parse(msg: &M) -> Result; } diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs index ce011f107..bd25de704 100644 --- a/actix-http/src/header/shared/content_encoding.rs +++ b/actix-http/src/header/shared/content_encoding.rs @@ -68,6 +68,7 @@ impl ContentEncoding { } impl Default for ContentEncoding { + #[inline] fn default() -> Self { Self::Identity } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9c1f56f64..0650b5508 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -102,12 +102,14 @@ actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.18", features = ["openssl"] } brotli2 = "0.3.2" +const-str = "0.3" env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" +zstd = "0.9" [[example]] name = "client" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 6eb6800d3..c1378157b 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + convert::Infallible, io::{Read, Write}, net::{IpAddr, Ipv4Addr}, sync::{ @@ -15,43 +16,16 @@ use cookie::Cookie; use futures_util::stream; use rand::Rng; -#[cfg(feature = "compress-brotli")] -use brotli2::write::BrotliEncoder; - -#[cfg(feature = "compress-gzip")] -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; - -use actix_http::{ContentEncoding, HttpService, StatusCode}; +use actix_http::{HttpService, StatusCode}; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; -use actix_web::{ - dev::{AppConfig, BodyEncoding}, - http::header, - web, App, Error, HttpRequest, HttpResponse, -}; +use actix_web::{dev::AppConfig, http::header, web, App, Error, HttpRequest, HttpResponse}; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; +mod utils; + +const S: &str = "Hello World "; +const STR: &str = const_str::repeat!(S, 100); #[actix_rt::test] async fn test_simple() { @@ -471,15 +445,12 @@ async fn test_no_decompress() { let srv = actix_test::start(|| { App::new() .wrap(actix_web::middleware::Compress::default()) - .service(web::resource("/").route(web::to(|| { - let mut res = HttpResponse::Ok().body(STR); - res.encode_with(header::ContentEncoding::Gzip); - res - }))) + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut res = awc::Client::new() .get(srv.url("/")) + .insert_header((header::ACCEPT_ENCODING, "gzip")) .no_decompress() .send() .await @@ -488,15 +459,12 @@ async fn test_no_decompress() { // read response let bytes = res.body().await.unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + assert_eq!(utils::gzip::decode(bytes), STR.as_bytes()); // POST let mut res = awc::Client::new() .post(srv.url("/")) + .insert_header((header::ACCEPT_ENCODING, "gzip")) .no_decompress() .send() .await @@ -504,10 +472,7 @@ async fn test_no_decompress() { assert!(res.status().is_success()); let bytes = res.body().await.unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + assert_eq!(utils::gzip::decode(bytes), STR.as_bytes()); } #[cfg(feature = "compress-gzip")] @@ -515,13 +480,9 @@ async fn test_no_decompress() { async fn test_client_gzip_encoding() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .insert_header(("content-encoding", "gzip")) - .body(data) + .insert_header(header::ContentEncoding::Gzip) + .body(utils::gzip::encode(STR)) }))) }); @@ -531,7 +492,7 @@ async fn test_client_gzip_encoding() { // read response let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + assert_eq!(bytes, STR); } #[cfg(feature = "compress-gzip")] @@ -539,13 +500,9 @@ async fn test_client_gzip_encoding() { async fn test_client_gzip_encoding_large() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.repeat(10).as_ref()).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .insert_header(("content-encoding", "gzip")) - .body(data) + .insert_header(header::ContentEncoding::Gzip) + .body(utils::gzip::encode(STR.repeat(10))) }))) }); @@ -555,7 +512,7 @@ async fn test_client_gzip_encoding_large() { // read response let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(STR.repeat(10))); + assert_eq!(bytes, STR.repeat(10)); } #[cfg(feature = "compress-gzip")] @@ -569,12 +526,9 @@ async fn test_client_gzip_encoding_large_random() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); HttpResponse::Ok() - .insert_header(("content-encoding", "gzip")) - .body(data) + .insert_header(header::ContentEncoding::Gzip) + .body(utils::gzip::encode(data)) }))) }); @@ -584,7 +538,7 @@ async fn test_client_gzip_encoding_large_random() { // read response let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); + assert_eq!(bytes, data); } #[cfg(feature = "compress-brotli")] @@ -592,12 +546,9 @@ async fn test_client_gzip_encoding_large_random() { async fn test_client_brotli_encoding() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); HttpResponse::Ok() .insert_header(("content-encoding", "br")) - .body(data) + .body(utils::brotli::encode(data)) }))) }); @@ -621,12 +572,9 @@ async fn test_client_brotli_encoding_large_random() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); HttpResponse::Ok() - .insert_header(("content-encoding", "br")) - .body(data) + .insert_header(header::ContentEncoding::Brotli) + .body(utils::brotli::encode(&data)) }))) }); @@ -636,27 +584,25 @@ async fn test_client_brotli_encoding_large_random() { // read response let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + assert_eq!(bytes, data); } #[actix_rt::test] async fn test_client_deflate_encoding() { let srv = actix_test::start(|| { - App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encode_with(ContentEncoding::Brotli) - .body(body) - })) + App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body))) }); - let req = srv.post("/").send_body(STR); + let req = srv + .post("/") + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .send_body(STR); let mut res = req.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + assert_eq!(bytes, STR); } #[actix_rt::test] @@ -668,14 +614,13 @@ async fn test_client_deflate_encoding_large_random() { .collect::(); let srv = actix_test::start(|| { - App::new().default_service(web::to(|body: Bytes| { - HttpResponse::Ok() - .encode_with(ContentEncoding::Brotli) - .body(body) - })) + App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body))) }); - let req = srv.post("/").send_body(data.clone()); + let req = srv + .post("/") + .insert_header((header::ACCEPT_ENCODING, "br")) + .send_body(data.clone()); let mut res = req.await.unwrap(); let bytes = res.body().await.unwrap(); @@ -688,15 +633,16 @@ async fn test_client_deflate_encoding_large_random() { async fn test_client_streaming_explicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { - HttpResponse::Ok() - .encode_with(ContentEncoding::Identity) - .streaming(body) + HttpResponse::Ok().streaming(body) })) }); let body = stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) }); - let req = srv.post("/").send_stream(Box::pin(body)); + let req = srv + .post("/") + .insert_header((header::ACCEPT_ENCODING, "identity")) + .send_stream(Box::pin(body)); let mut res = req.await.unwrap(); assert!(res.status().is_success()); @@ -709,17 +655,16 @@ async fn test_client_streaming_explicit() { async fn test_body_streaming_implicit() { let srv = actix_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() - .encode_with(ContentEncoding::Gzip) - .streaming(Box::pin(body)) + let body = + stream::once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_bytes())) }); + HttpResponse::Ok().streaming(body) })) }); - let req = srv.get("/").send(); + let req = srv + .get("/") + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .send(); let mut res = req.await.unwrap(); assert!(res.status().is_success()); diff --git a/tests/test_utils.rs b/awc/tests/utils.rs similarity index 100% rename from tests/test_utils.rs rename to awc/tests/utils.rs diff --git a/src/dev.rs b/src/dev.rs index 333d1acf8..def545ec7 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -22,8 +22,6 @@ pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, We pub use crate::types::{JsonBody, Readlines, UrlEncoded}; -use crate::{http::header::ContentEncoding, HttpMessage as _}; - use actix_router::Patterns; pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { @@ -44,79 +42,3 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { patterns } - -/// Helper trait for managing response encoding. -pub trait BodyEncoding { - /// Get content encoding - fn preferred_encoding(&self) -> Option; - - /// Set content encoding to use. - /// - /// Must be used with [`Compress`] to take effect. - /// - /// [`Compress`]: crate::middleware::Compress - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self; -} - -struct CompressWith(ContentEncoding); - -impl BodyEncoding for crate::HttpResponseBuilder { - fn preferred_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(CompressWith(encoding)); - self - } -} - -impl BodyEncoding for crate::HttpResponse { - fn preferred_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(CompressWith(encoding)); - self - } -} - -impl BodyEncoding for ServiceResponse { - fn preferred_encoding(&self) -> Option { - self.request() - .extensions() - .get::() - .map(|enc| enc.0) - } - - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { - self.request() - .extensions_mut() - .insert(CompressWith(encoding)); - self - } -} - -// TODO: remove these impls ? -impl BodyEncoding for actix_http::ResponseBuilder { - fn preferred_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(CompressWith(encoding)); - self - } -} - -impl BodyEncoding for actix_http::Response { - fn preferred_encoding(&self) -> Option { - self.extensions().get::().map(|enc| enc.0) - } - - fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(CompressWith(encoding)); - self - } -} diff --git a/src/http/header/entity.rs b/src/http/header/entity.rs index 76fe39f23..0eaa12b5d 100644 --- a/src/http/header/entity.rs +++ b/src/http/header/entity.rs @@ -17,8 +17,7 @@ fn check_slice_validity(slice: &str) -> bool { slice.bytes().all(entity_validate_char) } -/// An entity tag, defined -/// in [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3) +/// An entity tag, defined in [RFC 7232 §2.3]. /// /// An entity tag consists of a string enclosed by two literal double quotes. /// Preceding the first double quote is an optional weakness indicator, @@ -48,16 +47,20 @@ fn check_slice_validity(slice: &str) -> bool { /// | `W/"1"` | `W/"2"` | no match | no match | /// | `W/"1"` | `"1"` | no match | match | /// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] +/// +/// [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3) +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EntityTag { /// Weakness indicator for the tag pub weak: bool, + /// The opaque string in between the DQUOTEs tag: String, } impl EntityTag { - /// Constructs a new EntityTag. + /// Constructs a new `EntityTag`. + /// /// # Panics /// If the tag contains invalid characters. pub fn new(weak: bool, tag: String) -> EntityTag { @@ -66,51 +69,64 @@ impl EntityTag { } /// Constructs a new weak EntityTag. + /// /// # Panics /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { + pub fn new_weak(tag: String) -> EntityTag { EntityTag::new(true, tag) } + #[deprecated(since = "3.0.0", note = "Renamed to `new_weak`.")] + pub fn weak(tag: String) -> EntityTag { + Self::new_weak(tag) + } + /// Constructs a new strong EntityTag. + /// /// # Panics /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { + pub fn new_strong(tag: String) -> EntityTag { EntityTag::new(false, tag) } - /// Get the tag. + #[deprecated(since = "3.0.0", note = "Renamed to `new_strong`.")] + pub fn strong(tag: String) -> EntityTag { + Self::new_strong(tag) + } + + /// Returns tag. pub fn tag(&self) -> &str { self.tag.as_ref() } - /// Set the tag. + /// Sets tag. + /// /// # Panics /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { + pub fn set_tag(&mut self, tag: impl Into) { + let tag = tag.into(); assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); self.tag = tag } - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. + /// For strong comparison two entity-tags are equivalent if both are not weak and their + /// opaque-tags match character-by-character. pub fn strong_eq(&self, other: &EntityTag) -> bool { !self.weak && !other.weak && self.tag == other.tag } - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". + /// For weak comparison two entity-tags are equivalent if their opaque-tags match + /// character-by-character, regardless of either or both being tagged as "weak". pub fn weak_eq(&self, other: &EntityTag) -> bool { self.tag == other.tag } - /// The inverse of `EntityTag.strong_eq()`. + /// Returns the inverse of `strong_eq()`. pub fn strong_ne(&self, other: &EntityTag) -> bool { !self.strong_eq(other) } - /// The inverse of `EntityTag.weak_eq()`. + /// Returns inverse of `weak_eq()`. pub fn weak_ne(&self, other: &EntityTag) -> bool { !self.weak_eq(other) } @@ -178,23 +194,23 @@ mod tests { // Expected success assert_eq!( "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) + EntityTag::new_strong("foobar".to_owned()) ); assert_eq!( "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) + EntityTag::new_strong("".to_owned()) ); assert_eq!( "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) + EntityTag::new_weak("weaktag".to_owned()) ); assert_eq!( "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) + EntityTag::new_weak("\x65\x62".to_owned()) ); assert_eq!( "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) + EntityTag::new_weak("".to_owned()) ); } @@ -214,19 +230,19 @@ mod tests { #[test] fn test_etag_fmt() { assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), + format!("{}", EntityTag::new_strong("foobar".to_owned())), "\"foobar\"" ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); + assert_eq!(format!("{}", EntityTag::new_strong("".to_owned())), "\"\""); assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), + format!("{}", EntityTag::new_weak("weak-etag".to_owned())), "W/\"weak-etag\"" ); assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), + format!("{}", EntityTag::new_weak("\u{0065}".to_owned())), "W/\"\x65\"" ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + assert_eq!(format!("{}", EntityTag::new_weak("".to_owned())), "W/\"\""); } #[test] @@ -237,29 +253,29 @@ mod tests { // | `W/"1"` | `W/"2"` | no match | no match | // | `W/"1"` | `"1"` | no match | match | // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); + let mut etag1 = EntityTag::new_weak("1".to_owned()); + let mut etag2 = EntityTag::new_weak("1".to_owned()); assert!(!etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(!etag1.weak_ne(&etag2)); - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); + etag1 = EntityTag::new_weak("1".to_owned()); + etag2 = EntityTag::new_weak("2".to_owned()); assert!(!etag1.strong_eq(&etag2)); assert!(!etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(etag1.weak_ne(&etag2)); - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); + etag1 = EntityTag::new_weak("1".to_owned()); + etag2 = EntityTag::new_strong("1".to_owned()); assert!(!etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(!etag1.weak_ne(&etag2)); - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); + etag1 = EntityTag::new_strong("1".to_owned()); + etag2 = EntityTag::new_strong("1".to_owned()); assert!(etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(!etag1.strong_ne(&etag2)); diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index 4724c917e..78f5447b3 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -31,7 +31,7 @@ crate::http::header::common_header! { /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// ETag(EntityTag::new(false, "xyzzy".to_owned())) + /// ETag(EntityTag::new_strong("xyzzy".to_owned())) /// ); /// ``` /// @@ -41,7 +41,7 @@ crate::http::header::common_header! { /// /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( - /// ETag(EntityTag::new(true, "xyzzy".to_owned())) + /// ETag(EntityTag::new_weak("xyzzy".to_owned())) /// ); /// ``` (ETag, ETAG) => [EntityTag] @@ -50,29 +50,29 @@ crate::http::header::common_header! { // From the RFC crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); + Some(ETag(EntityTag::new_strong("xyzzy".to_owned())))); crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); + Some(ETag(EntityTag::new_weak("xyzzy".to_owned())))); crate::http::header::common_header_test!(test3, vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); + Some(ETag(EntityTag::new_strong("".to_owned())))); // Own tests crate::http::header::common_header_test!(test4, vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); + Some(ETag(EntityTag::new_strong("foobar".to_owned())))); crate::http::header::common_header_test!(test5, vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); + Some(ETag(EntityTag::new_strong("".to_owned())))); crate::http::header::common_header_test!(test6, vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); + Some(ETag(EntityTag::new_weak("weak-etag".to_owned())))); crate::http::header::common_header_test!(test7, vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); + Some(ETag(EntityTag::new_weak("\u{0065}\u{0062}".to_owned())))); crate::http::header::common_header_test!(test8, vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); + Some(ETag(EntityTag::new_weak("".to_owned())))); crate::http::header::common_header_test!(test9, vec![b"no-dquotes"], None::); diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index a565b9125..e299d30fe 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -54,14 +54,15 @@ common_header! { test1, vec![b"\"xyzzy\""], Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); + vec![EntityTag::new_strong("xyzzy".to_owned())]))); + crate::http::header::common_header_test!( test2, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); + vec![EntityTag::new_strong("xyzzy".to_owned()), + EntityTag::new_strong("r2d2xxxx".to_owned()), + EntityTag::new_strong("c3piozzzz".to_owned())]))); crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); } } diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index fb1895fc8..863be70cf 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -82,8 +82,8 @@ mod tests { if_none_match = Header::parse(&req); let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); + let foobar_etag = EntityTag::new_strong("foobar".to_owned()); + let weak_etag = EntityTag::new_weak("weak-etag".to_owned()); entities.push(foobar_etag); entities.push(weak_etag); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index bdd193693..16af4c2cd 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,7 +16,6 @@ use pin_project_lite::pin_project; use crate::{ body::{EitherBody, MessageBody}, - dev::BodyEncoding as _, http::{ header::{self, AcceptEncoding, Encoding, HeaderValue}, StatusCode, @@ -176,14 +175,10 @@ where match ready!(this.fut.poll(cx)) { Ok(resp) => { - let enc = if let Some(enc) = resp.response().preferred_encoding() { - enc - } else { - match this.encoding { - Encoding::Known(enc) => *enc, - Encoding::Unknown(enc) => { - unimplemented!("encoding {} should not be here", enc); - } + let enc = match this.encoding { + Encoding::Known(enc) => *enc, + Encoding::Unknown(enc) => { + unimplemented!("encoding {} should not be here", enc); } }; diff --git a/tests/compression.rs b/tests/compression.rs index c0bf10e4c..88c462f60 100644 --- a/tests/compression.rs +++ b/tests/compression.rs @@ -1,14 +1,12 @@ use actix_http::ContentEncoding; use actix_web::{ - dev::BodyEncoding as _, http::{header, StatusCode}, middleware::Compress, web, App, HttpResponse, }; use bytes::Bytes; -mod test_utils; -use test_utils::{brotli, gzip, zstd}; +mod utils; static LOREM: &[u8] = include_bytes!("fixtures/lorem.txt"); static LOREM_GZIP: &[u8] = include_bytes!("fixtures/lorem.txt.gz"); @@ -27,7 +25,6 @@ macro_rules! test_server { web::to(|| { HttpResponse::Ok() // signal to compressor that content should not be altered - .encode_with(ContentEncoding::Identity) // signal to client that content is encoded .insert_header(ContentEncoding::Gzip) .body(LOREM_GZIP) @@ -38,7 +35,6 @@ macro_rules! test_server { web::to(|| { HttpResponse::Ok() // signal to compressor that content should not be altered - .encode_with(ContentEncoding::Identity) // signal to client that content is encoded .insert_header(ContentEncoding::Brotli) .body(LOREM_BR) @@ -49,7 +45,6 @@ macro_rules! test_server { web::to(|| { HttpResponse::Ok() // signal to compressor that content should not be altered - .encode_with(ContentEncoding::Identity) // signal to client that content is encoded .insert_header(ContentEncoding::Zstd) .body(LOREM_ZSTD) @@ -60,7 +55,6 @@ macro_rules! test_server { web::to(|| { HttpResponse::Ok() // signal to compressor that content should not be altered - .encode_with(ContentEncoding::Identity) // signal to client that content is encoded as 7zip .insert_header((header::CONTENT_ENCODING, "xz")) .body(LOREM_XZ) @@ -117,7 +111,7 @@ async fn negotiate_encoding_gzip() { .await .unwrap(); let bytes = res.body().await.unwrap(); - assert_eq!(gzip::decode(bytes), LOREM); + assert_eq!(utils::gzip::decode(bytes), LOREM); srv.stop().await; } @@ -146,7 +140,7 @@ async fn negotiate_encoding_br() { .await .unwrap(); let bytes = res.body().await.unwrap(); - assert_eq!(brotli::decode(bytes), LOREM); + assert_eq!(utils::brotli::decode(bytes), LOREM); srv.stop().await; } @@ -175,7 +169,7 @@ async fn negotiate_encoding_zstd() { .await .unwrap(); let bytes = res.body().await.unwrap(); - assert_eq!(zstd::decode(bytes), LOREM); + assert_eq!(utils::zstd::decode(bytes), LOREM); srv.stop().await; } diff --git a/tests/test_server.rs b/tests/test_server.rs index e8d514e64..987e51a65 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -12,11 +12,7 @@ use std::{ use actix_web::{ cookie::{Cookie, CookieBuilder}, - dev::BodyEncoding, - http::{ - header::{self, ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, TRANSFER_ENCODING}, - StatusCode, - }, + http::{header, StatusCode}, middleware::{Compress, NormalizePath, TrailingSlash}, web, App, Error, HttpResponse, }; @@ -31,30 +27,10 @@ use openssl::{ x509::X509, }; -mod test_utils; -use test_utils::{brotli, deflate, gzip, zstd}; +mod utils; -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; +const S: &str = "Hello World "; +const STR: &str = const_str::repeat!(S, 100); #[cfg(feature = "openssl")] fn openssl_config() -> SslAcceptor { @@ -129,51 +105,52 @@ async fn test_body() { srv.stop().await; } -#[actix_rt::test] -async fn test_body_encoding_override() { - let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new() - .wrap(Compress::default()) - .service(web::resource("/").route(web::to(|| { - HttpResponse::Ok() - .encode_with(ContentEncoding::Deflate) - .body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let mut res = HttpResponse::with_body(actix_web::http::StatusCode::OK, STR); - res.encode_with(ContentEncoding::Deflate); - res.map_into_boxed_body() - }))) - }); +// enforcing an encoding per-response is removed +// #[actix_rt::test] +// async fn test_body_encoding_override() { +// let srv = actix_test::start_with(actix_test::config().h1(), || { +// App::new() +// .wrap(Compress::default()) +// .service(web::resource("/").route(web::to(|| { +// HttpResponse::Ok() +// .encode_with(ContentEncoding::Deflate) +// .body(STR) +// }))) +// .service(web::resource("/raw").route(web::to(|| { +// let mut res = HttpResponse::with_body(actix_web::http::StatusCode::OK, STR); +// res.encode_with(ContentEncoding::Deflate); +// res.map_into_boxed_body() +// }))) +// }); - // Builder - let mut res = srv - .get("/") - .no_decompress() - .append_header((ACCEPT_ENCODING, "deflate")) - .send() - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::OK); +// // Builder +// let mut res = srv +// .get("/") +// .no_decompress() +// .append_header((ACCEPT_ENCODING, "deflate")) +// .send() +// .await +// .unwrap(); +// assert_eq!(res.status(), StatusCode::OK); - let bytes = res.body().await.unwrap(); - assert_eq!(deflate::decode(bytes), STR.as_bytes()); +// let bytes = res.body().await.unwrap(); +// assert_eq!(utils::deflate::decode(bytes), STR.as_bytes()); - // Raw Response - let mut res = srv - .request(actix_web::http::Method::GET, srv.url("/raw")) - .no_decompress() - .append_header((ACCEPT_ENCODING, "deflate")) - .send() - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::OK); +// // Raw Response +// let mut res = srv +// .request(actix_web::http::Method::GET, srv.url("/raw")) +// .no_decompress() +// .append_header((ACCEPT_ENCODING, "deflate")) +// .send() +// .await +// .unwrap(); +// assert_eq!(res.status(), StatusCode::OK); - let bytes = res.body().await.unwrap(); - assert_eq!(deflate::decode(bytes), STR.as_bytes()); +// let bytes = res.body().await.unwrap(); +// assert_eq!(utils::deflate::decode(bytes), STR.as_bytes()); - srv.stop().await; -} +// srv.stop().await; +// } #[actix_rt::test] async fn body_gzip_large() { @@ -191,14 +168,14 @@ async fn body_gzip_large() { let mut res = srv .get("/") .no_decompress() - .append_header((ACCEPT_ENCODING, "gzip")) + .append_header((header::ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(gzip::decode(bytes), data.as_bytes()); + assert_eq!(utils::gzip::decode(bytes), data.as_bytes()); srv.stop().await; } @@ -222,14 +199,14 @@ async fn test_body_gzip_large_random() { let mut res = srv .get("/") .no_decompress() - .append_header((ACCEPT_ENCODING, "gzip")) + .append_header((header::ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(gzip::decode(bytes), data.as_bytes()); + assert_eq!(utils::gzip::decode(bytes), data.as_bytes()); srv.stop().await; } @@ -248,15 +225,18 @@ async fn test_body_chunked_implicit() { let mut res = srv .get("/") .no_decompress() - .append_header((ACCEPT_ENCODING, "gzip")) + .append_header((header::ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers().get(TRANSFER_ENCODING).unwrap(), "chunked"); + assert_eq!( + res.headers().get(header::TRANSFER_ENCODING).unwrap(), + "chunked" + ); let bytes = res.body().await.unwrap(); - assert_eq!(gzip::decode(bytes), STR.as_bytes()); + assert_eq!(utils::gzip::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -274,7 +254,7 @@ async fn test_body_br_streaming() { let mut res = srv .get("/") - .append_header((ACCEPT_ENCODING, "br")) + .append_header((header::ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -282,7 +262,7 @@ async fn test_body_br_streaming() { assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(brotli::decode(bytes), STR.as_bytes()); + assert_eq!(utils::brotli::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -319,7 +299,7 @@ async fn test_no_chunking() { let mut res = srv.get("/").send().await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert!(!res.headers().contains_key(TRANSFER_ENCODING)); + assert!(!res.headers().contains_key(header::TRANSFER_ENCODING)); let bytes = res.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -337,7 +317,7 @@ async fn test_body_deflate() { let mut res = srv .get("/") - .append_header((ACCEPT_ENCODING, "deflate")) + .append_header((header::ACCEPT_ENCODING, "deflate")) .no_decompress() .send() .await @@ -345,7 +325,7 @@ async fn test_body_deflate() { assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(deflate::decode(bytes), STR.as_bytes()); + assert_eq!(utils::deflate::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -360,7 +340,7 @@ async fn test_body_brotli() { let mut res = srv .get("/") - .append_header((ACCEPT_ENCODING, "br")) + .append_header((header::ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -368,7 +348,7 @@ async fn test_body_brotli() { assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(brotli::decode(bytes), STR.as_bytes()); + assert_eq!(utils::brotli::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -383,7 +363,7 @@ async fn test_body_zstd() { let mut res = srv .get("/") - .append_header((ACCEPT_ENCODING, "zstd")) + .append_header((header::ACCEPT_ENCODING, "zstd")) .no_decompress() .send() .await @@ -391,7 +371,7 @@ async fn test_body_zstd() { assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(zstd::decode(bytes), STR.as_bytes()); + assert_eq!(utils::zstd::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -409,7 +389,7 @@ async fn test_body_zstd_streaming() { let mut res = srv .get("/") - .append_header((ACCEPT_ENCODING, "zstd")) + .append_header((header::ACCEPT_ENCODING, "zstd")) .no_decompress() .send() .await @@ -417,7 +397,7 @@ async fn test_body_zstd_streaming() { assert_eq!(res.status(), StatusCode::OK); let bytes = res.body().await.unwrap(); - assert_eq!(zstd::decode(bytes), STR.as_bytes()); + assert_eq!(utils::zstd::decode(bytes), STR.as_bytes()); srv.stop().await; } @@ -432,8 +412,8 @@ async fn test_zstd_encoding() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "zstd")) - .send_body(zstd::encode(STR)); + .append_header((header::CONTENT_ENCODING, "zstd")) + .send_body(utils::zstd::encode(STR)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -463,8 +443,8 @@ async fn test_zstd_encoding_large() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "zstd")) - .send_body(zstd::encode(&data)); + .append_header((header::CONTENT_ENCODING, "zstd")) + .send_body(utils::zstd::encode(&data)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -484,8 +464,8 @@ async fn test_encoding() { let request = srv .post("/") - .insert_header((CONTENT_ENCODING, "gzip")) - .send_body(gzip::encode(STR)); + .insert_header((header::CONTENT_ENCODING, "gzip")) + .send_body(utils::gzip::encode(STR)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -505,8 +485,8 @@ async fn test_gzip_encoding() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "gzip")) - .send_body(gzip::encode(STR)); + .append_header((header::CONTENT_ENCODING, "gzip")) + .send_body(utils::gzip::encode(STR)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -527,8 +507,8 @@ async fn test_gzip_encoding_large() { let req = srv .post("/") - .append_header((CONTENT_ENCODING, "gzip")) - .send_body(gzip::encode(&data)); + .append_header((header::CONTENT_ENCODING, "gzip")) + .send_body(utils::gzip::encode(&data)); let mut res = req.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -554,8 +534,8 @@ async fn test_reading_gzip_encoding_large_random() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "gzip")) - .send_body(gzip::encode(&data)); + .append_header((header::CONTENT_ENCODING, "gzip")) + .send_body(utils::gzip::encode(&data)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -575,8 +555,8 @@ async fn test_reading_deflate_encoding() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "deflate")) - .send_body(deflate::encode(STR)); + .append_header((header::CONTENT_ENCODING, "deflate")) + .send_body(utils::deflate::encode(STR)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -597,8 +577,8 @@ async fn test_reading_deflate_encoding_large() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "deflate")) - .send_body(deflate::encode(&data)); + .append_header((header::CONTENT_ENCODING, "deflate")) + .send_body(utils::deflate::encode(&data)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -624,8 +604,8 @@ async fn test_reading_deflate_encoding_large_random() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "deflate")) - .send_body(deflate::encode(&data)); + .append_header((header::CONTENT_ENCODING, "deflate")) + .send_body(utils::deflate::encode(&data)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -646,8 +626,8 @@ async fn test_brotli_encoding() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "br")) - .send_body(brotli::encode(STR)); + .append_header((header::CONTENT_ENCODING, "br")) + .send_body(utils::brotli::encode(STR)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -677,8 +657,8 @@ async fn test_brotli_encoding_large() { let request = srv .post("/") - .append_header((CONTENT_ENCODING, "br")) - .send_body(brotli::encode(&data)); + .append_header((header::CONTENT_ENCODING, "br")) + .send_body(utils::brotli::encode(&data)); let mut res = request.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -697,8 +677,9 @@ async fn test_brotli_encoding_large_openssl() { let srv = actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + // echo decompressed request body back in response HttpResponse::Ok() - .encode_with(ContentEncoding::Identity) + .insert_header(header::ContentEncoding::Identity) .body(bytes) }))) }); @@ -706,7 +687,7 @@ async fn test_brotli_encoding_large_openssl() { let mut res = srv .post("/") .append_header((header::CONTENT_ENCODING, "br")) - .send_body(brotli::encode(&data)) + .send_body(utils::brotli::encode(&data)) .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -758,16 +739,20 @@ mod plus_rustls { let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + // echo decompressed request body back in response HttpResponse::Ok() - .encode_with(ContentEncoding::Identity) + .insert_header(header::ContentEncoding::Identity) .body(bytes) }))) }); let req = srv .post("/") - .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) - .send_stream(TestBody::new(Bytes::from(deflate::encode(&data)), 1024)); + .insert_header((header::CONTENT_ENCODING, "deflate")) + .send_stream(TestBody::new( + Bytes::from(utils::deflate::encode(&data)), + 1024, + )); let mut res = req.await.unwrap(); assert_eq!(res.status(), StatusCode::OK); @@ -931,14 +916,14 @@ async fn test_accept_encoding_no_match() { let mut res = srv .get("/") - .insert_header((ACCEPT_ENCODING, "xz, identity;q=0")) + .insert_header((header::ACCEPT_ENCODING, "xz, identity;q=0")) .no_decompress() .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); - assert_eq!(res.headers().get(CONTENT_ENCODING), None); + assert_eq!(res.headers().get(header::CONTENT_ENCODING), None); let bytes = res.body().await.unwrap(); // body should contain the supported encodings diff --git a/tests/utils.rs b/tests/utils.rs new file mode 100644 index 000000000..9a3743d8b --- /dev/null +++ b/tests/utils.rs @@ -0,0 +1,76 @@ +// compiling some tests will trigger unused function warnings even though other tests use them +#![allow(dead_code)] + +use std::io::{Read as _, Write as _}; + +pub mod gzip { + use super::*; + use flate2::{read::GzDecoder, write::GzEncoder, Compression}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = GzEncoder::new(Vec::new(), Compression::fast()); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = GzDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod deflate { + use super::*; + use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::fast()); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = ZlibDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod brotli { + use super::*; + use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = BrotliEncoder::new(Vec::new(), 3); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = BrotliDecoder::new(bytes.as_ref()); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} + +pub mod zstd { + use super::*; + use ::zstd::stream::{read::Decoder, write::Encoder}; + + pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { + let mut encoder = Encoder::new(Vec::new(), 3).unwrap(); + encoder.write_all(bytes.as_ref()).unwrap(); + encoder.finish().unwrap() + } + + pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { + let mut decoder = Decoder::new(bytes.as_ref()).unwrap(); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } +} From c7639bc3be5b1779d573dc1d2c0d0396b4968554 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 03:48:12 +0000 Subject: [PATCH 248/861] document quoter --- actix-files/src/service.rs | 8 +-- actix-router/src/url.rs | 141 +++++++++++++++++++++++++------------ 2 files changed, 100 insertions(+), 49 deletions(-) diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 057dbe5a3..152e1855e 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -114,7 +114,7 @@ impl Service for FilesService { Box::pin(async move { if !is_method_valid { return Ok(req.into_response( - actix_web::HttpResponse::MethodNotAllowed() + HttpResponse::MethodNotAllowed() .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .body("Request did not meet this resource's requirements."), )); @@ -123,7 +123,7 @@ impl Service for FilesService { let real_path = match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) { Ok(item) => item, - Err(e) => return Ok(req.error_response(e)), + Err(err) => return Ok(req.error_response(err)), }; if let Some(filter) = &this.path_filter { @@ -131,9 +131,7 @@ impl Service for FilesService { if let Some(ref default) = this.default { return default.call(req).await; } else { - return Ok( - req.into_response(actix_web::HttpResponse::NotFound().finish()) - ); + return Ok(req.into_response(HttpResponse::NotFound().finish())); } } } diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index 10193dde8..fee8eaaf3 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -26,16 +26,6 @@ const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz const QS: &[u8] = b"+&=;b"; -#[inline] -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 -} - -#[inline] -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 1 << (ch & 7) -} - thread_local! { static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); } @@ -96,7 +86,10 @@ impl ResourcePath for Url { /// A quoter pub struct Quoter { + /// Simple bit-map of safe values in the 0-127 ASCII range. safe_table: [u8; 16], + + /// Simple bit-map of protected values in the 0-127 ASCII range. protected_table: [u8; 16], } @@ -108,28 +101,32 @@ impl Quoter { }; // prepare safe table - for i in 0..128 { - if ALLOWED.contains(&i) { - set_bit(&mut quoter.safe_table, i); + for ch in 0..128 { + if ALLOWED.contains(&ch) { + set_bit(&mut quoter.safe_table, ch); } - if QS.contains(&i) { - set_bit(&mut quoter.safe_table, i); + + if QS.contains(&ch) { + set_bit(&mut quoter.safe_table, ch); } } - for ch in safe { - set_bit(&mut quoter.safe_table, *ch) + for &ch in safe { + set_bit(&mut quoter.safe_table, ch) } // prepare protected table - for ch in protected { - set_bit(&mut quoter.safe_table, *ch); - set_bit(&mut quoter.protected_table, *ch); + for &ch in protected { + set_bit(&mut quoter.safe_table, ch); + set_bit(&mut quoter.protected_table, ch); } quoter } + /// Re-quotes... ? + /// + /// Returns `None` when no modification to the original string was required. pub fn requote(&self, val: &[u8]) -> Option { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; @@ -137,17 +134,19 @@ impl Quoter { let mut cloned: Option> = None; let len = val.len(); + while idx < len { let ch = val[idx]; if has_pct != 0 { pct[has_pct] = val[idx]; has_pct += 1; + if has_pct == 3 { has_pct = 0; let buf = cloned.as_mut().unwrap(); - if let Some(ch) = restore_ch(pct[1], pct[2]) { + if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { if ch < 128 { if bit_at(&self.protected_table, ch) { buf.extend_from_slice(&pct); @@ -161,6 +160,7 @@ impl Quoter { continue; } } + buf.push(ch); } else { buf.extend_from_slice(&pct[..]); @@ -168,6 +168,7 @@ impl Quoter { } } else if ch == b'%' { has_pct = 1; + if cloned.is_none() { let mut c = Vec::with_capacity(len); c.extend_from_slice(&val[..idx]); @@ -176,6 +177,7 @@ impl Quoter { } else if let Some(ref mut cloned) = cloned { cloned.push(ch) } + idx += 1; } @@ -183,22 +185,52 @@ impl Quoter { } } -#[inline] -fn from_hex(v: u8) -> Option { - if (b'0'..=b'9').contains(&v) { - Some(v - 0x30) // ord('0') == 0x30 - } else if (b'A'..=b'F').contains(&v) { - Some(v - 0x41 + 10) // ord('A') == 0x41 - } else if (b'a'..=b'f').contains(&v) { - Some(v - 0x61 + 10) // ord('a') == 0x61 - } else { - None +/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer +/// representation from `0x0`–`0xF`. +/// +/// - `0x30 ('0') => 0x0` +/// - `0x39 ('9') => 0x9` +/// - `0x41 ('a') => 0xA` +/// - `0x61 ('A') => 0xA` +/// - `0x46 ('f') => 0xF` +/// - `0x66 ('F') => 0xF` +fn from_ascii_hex(v: u8) -> Option { + match v { + b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 + b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 + b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 + _ => None, } } -#[inline] -fn restore_ch(d1: u8, d2: u8) -> Option { - from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2)) +/// Decode a ASCII hex-encoded pair to an integer. +/// +/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. +/// +/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` +/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` +/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` +fn hex_pair_to_char(d1: u8, d2: u8) -> Option { + let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); + + // left shift high nibble by 4 bits + Some(d_high << 4 | d_low) +} + +/// Sets bit in given bit-map to 1=true. +/// +/// # Panics +/// Panics if `ch` index is out of bounds. +fn set_bit(array: &mut [u8], ch: u8) { + array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) +} + +/// Returns true if bit to true in given bit-map. +/// +/// # Panics +/// Panics if `ch` index is out of bounds. +fn bit_at(array: &[u8], ch: u8) -> bool { + array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 } #[cfg(test)] @@ -229,6 +261,16 @@ mod tests { let path = match_url(re, "/user/2345/test"); assert_eq!(path.get("id").unwrap(), "2345"); + } + + #[test] + fn protected_chars() { + let re = "/user/{id}/test"; + + let encoded = percent_encode(PROTECTED); + let path = match_url(re, format!("/user/{}/test", encoded)); + // characters in captured segment remain unencoded + assert_eq!(path.get("id").unwrap(), &encoded); // "%25" should never be decoded into '%' to guarantee the output is a valid // percent-encoded format @@ -239,13 +281,6 @@ mod tests { assert_eq!(path.get("id").unwrap(), "qwe%25rty"); } - #[test] - fn protected_chars() { - let encoded = percent_encode(PROTECTED); - let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); - assert_eq!(path.get("id").unwrap(), &encoded); - } - #[test] fn non_protected_ascii() { let non_protected_ascii = ('\u{0}'..='\u{7F}') @@ -281,9 +316,9 @@ mod tests { for i in 0..256 { let c = i as u8; if hex.contains(&c) { - assert!(from_hex(c).is_some()) + assert!(from_ascii_hex(c).is_some()) } else { - assert!(from_hex(c).is_none()) + assert!(from_ascii_hex(c).is_none()) } } @@ -291,7 +326,25 @@ mod tests { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, ]; for i in 0..hex.len() { - assert_eq!(from_hex(hex[i]).unwrap(), expected[i]); + assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); } } + + #[test] + fn custom_quoter() { + let q = Quoter::new(b"", b"+"); + assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c"); + assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc"); + + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); + assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); + assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); + } + + #[test] + fn quoter_no_modification() { + let q = Quoter::new(b"", b""); + assert_eq!(q.requote(b"/abc/../efg"), None); + } } From 93754f307f4e3ffc1e895ccb87d8ad61368d178c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 04:08:46 +0000 Subject: [PATCH 249/861] try path config from Data as well --- actix-files/src/directory.rs | 13 +++++++++++-- actix-router/src/url.rs | 1 + src/types/path.rs | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 80e0c98d0..26225ea5c 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -40,14 +40,23 @@ impl Directory { pub(crate) type DirectoryRenderer = dyn Fn(&Directory, &HttpRequest) -> Result; -// show file url as relative to static path +/// Returns percent encoded file URL path. macro_rules! encode_file_url { ($path:ident) => { utf8_percent_encode(&$path, CONTROLS) }; } -// " -- " & -- & ' -- ' < -- < > -- > / -- / +/// Returns HTML entity encoded formatter. +/// +/// ```plain +/// " => " +/// & => & +/// ' => ' +/// < => < +/// > => > +/// / => / +/// ``` macro_rules! encode_file_name { ($entry:ident) => { escape_html_entity(&$entry.file_name().to_string_lossy(), Html) diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index fee8eaaf3..156c1e1c6 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -340,6 +340,7 @@ mod tests { assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); + assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb"); } #[test] diff --git a/src/types/path.rs b/src/types/path.rs index 4b60d27c0..4a694b763 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -9,6 +9,7 @@ use serde::de; use crate::{ dev::Payload, error::{Error, ErrorNotFound, PathError}, + web::Data, FromRequest, HttpRequest, }; @@ -102,6 +103,7 @@ where fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req .app_data::() + .or_else(|| req.app_data::>().map(Data::get_ref)) .and_then(|c| c.err_handler.clone()); ready( @@ -113,6 +115,7 @@ where Request path: {:?}", req.path() ); + if let Some(error_handler) = error_handler { let e = PathError::Deserialize(err); (error_handler)(e, req) From 374dc9bfc92176635c1b54ff112cefdb06e64646 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 4 Jan 2022 15:54:11 +0300 Subject: [PATCH 250/861] files: percent-decode url path (#2398) Co-authored-by: Rob Ede --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 1 + actix-files/src/error.rs | 7 +++++++ actix-files/src/files.rs | 1 + actix-files/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ actix-files/src/path_buf.rs | 24 +++++++++++++++++++++++- 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c626fd3fb..501181d92 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,12 @@ # Changes ## Unreleased - 2021-xx-xx +- `Files`: request URL paths with `%2F` are now rejected. [#2398] +- `Files`: Fixed a regression where `%25` in the URL path is not decoded to `%` in the file path. [#2398] - Minimum supported Rust version (MSRV) is now 1.54. +[#2398]: https://github.com/actix/actix-web/pull/2398 + ## 0.6.0-beta.12 - 2021-12-29 - No significant changes since `0.6.0-beta.11`. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c2acbc761..745e3afee 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -45,3 +45,4 @@ tokio-uring = { version = "0.1", optional = true } actix-rt = "2.2" actix-test = "0.1.0-beta.10" actix-web = "4.0.0-beta.18" +tempfile = "3.2" diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index f8e32eef7..d28889e73 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -23,16 +23,23 @@ impl ResponseError for FilesError { #[allow(clippy::enum_variant_names)] #[derive(Display, Debug, PartialEq)] +#[non_exhaustive] pub enum UriSegmentError { /// The segment started with the wrapped invalid character. #[display(fmt = "The segment started with the wrapped invalid character")] BadStart(char), + /// The segment contained the wrapped invalid character. #[display(fmt = "The segment contained the wrapped invalid character")] BadChar(char), + /// The segment ended with the wrapped invalid character. #[display(fmt = "The segment ended with the wrapped invalid character")] BadEnd(char), + + /// The path is not a valid UTF-8 string after doing percent decoding. + #[display(fmt = "The path is not a valif UTF-8 string after percent-decoding")] + NotValidUtf8, } /// Return `BadRequest` for `UriSegmentError` diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index d1dd6739d..adfb93232 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -28,6 +28,7 @@ use crate::{ /// /// `Files` service must be registered with `App::service()` method. /// +/// # Examples /// ``` /// use actix_web::App; /// use actix_files::Files; diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8ed7d44e0..a11aa32c7 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -803,6 +803,38 @@ mod tests { let req = TestRequest::get().uri("/test/%43argo.toml").to_request(); let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); + + // `%2F` == `/` + let req = TestRequest::get().uri("/test%2Ftest.binary").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_percent_encoding_2() { + let tmpdir = tempfile::tempdir().unwrap(); + let filename = match cfg!(unix) { + true => "ض:?#[]{}<>()@!$&'`|*+,;= %20.test", + false => "ض#[]{}()@!$&'`+,;= %20.test", + }; + let filename_encoded = filename + .as_bytes() + .iter() + .map(|c| format!("%{:02X}", c)) + .collect::(); + std::fs::File::create(tmpdir.path().join(filename)).unwrap(); + + let srv = test::init_service(App::new().service(Files::new("", tmpdir.path()))).await; + + let req = TestRequest::get() + .uri(&format!("/{}", filename_encoded)) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); } #[actix_rt::test] diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 0e0d4f51d..03b2cd766 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -1,5 +1,5 @@ use std::{ - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, str::FromStr, }; @@ -26,8 +26,23 @@ impl PathBufWrap { pub fn parse_path(path: &str, hidden_files: bool) -> Result { let mut buf = PathBuf::new(); + // equivalent to `path.split('/').count()` + let mut segment_count = path.matches('/').count() + 1; + + // we can decode the whole path here (instead of per-segment decoding) + // because we will reject `%2F` in paths using `segement_count`. + let path = percent_encoding::percent_decode_str(path) + .decode_utf8() + .map_err(|_| UriSegmentError::NotValidUtf8)?; + + // disallow decoding `%2F` into `/` + if segment_count != path.matches('/').count() + 1 { + return Err(UriSegmentError::BadChar('/')); + } + for segment in path.split('/') { if segment == ".." { + segment_count -= 1; buf.pop(); } else if !hidden_files && segment.starts_with('.') { return Err(UriSegmentError::BadStart('.')); @@ -40,6 +55,7 @@ impl PathBufWrap { } else if segment.ends_with('<') { return Err(UriSegmentError::BadEnd('<')); } else if segment.is_empty() { + segment_count -= 1; continue; } else if cfg!(windows) && segment.contains('\\') { return Err(UriSegmentError::BadChar('\\')); @@ -48,6 +64,12 @@ impl PathBufWrap { } } + // make sure we agree with stdlib parser + for (i, component) in buf.components().enumerate() { + assert!(matches!(component, Component::Normal(_))); + assert!(i < segment_count); + } + Ok(PathBufWrap(buf)) } } From 577597a80a775c6cab8d3b905aff5dc70fdd4b2e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 12:09:48 +0000 Subject: [PATCH 251/861] rename on-connect example --- Cargo.toml | 2 +- examples/{on_connect.rs => on-connect.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{on_connect.rs => on-connect.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 3118f3b03..5c4abe46f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,7 +165,7 @@ name = "uds" required-features = ["compress-gzip"] [[example]] -name = "on_connect" +name = "on-connect" required-features = [] [[bench]] diff --git a/examples/on_connect.rs b/examples/on-connect.rs similarity index 100% rename from examples/on_connect.rs rename to examples/on-connect.rs From 85c9b1a263366d279d544cddf407d00989372269 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 12:58:40 +0000 Subject: [PATCH 252/861] move quoter --- actix-files/CHANGES.md | 4 +- actix-files/src/error.rs | 2 +- actix-router/src/lib.rs | 4 +- actix-router/src/quoter.rs | 219 +++++++++++++++++++++++++++++++++++++ actix-router/src/url.rs | 219 +------------------------------------ src/request.rs | 2 +- src/types/path.rs | 27 +++-- tests/weird_poll.rs | 30 +++++ 8 files changed, 273 insertions(+), 234 deletions(-) create mode 100644 actix-router/src/quoter.rs create mode 100644 tests/weird_poll.rs diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 501181d92..93a122941 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx -- `Files`: request URL paths with `%2F` are now rejected. [#2398] -- `Files`: Fixed a regression where `%25` in the URL path is not decoded to `%` in the file path. [#2398] +- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398] +- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398] - Minimum supported Rust version (MSRV) is now 1.54. [#2398]: https://github.com/actix/actix-web/pull/2398 diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index d28889e73..6682529f8 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -38,7 +38,7 @@ pub enum UriSegmentError { BadEnd(char), /// The path is not a valid UTF-8 string after doing percent decoding. - #[display(fmt = "The path is not a valif UTF-8 string after percent-decoding")] + #[display(fmt = "The path is not a valid UTF-8 string after percent-decoding")] NotValidUtf8, } diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 03f464626..22f294b9d 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -8,6 +8,7 @@ mod de; mod path; mod pattern; +mod quoter; mod resource; mod resource_path; mod router; @@ -18,9 +19,10 @@ mod url; pub use self::de::PathDeserializer; pub use self::path::Path; pub use self::pattern::{IntoPatterns, Patterns}; +pub use self::quoter::Quoter; pub use self::resource::ResourceDef; pub use self::resource_path::{Resource, ResourcePath}; pub use self::router::{ResourceInfo, Router, RouterBuilder}; #[cfg(feature = "http")] -pub use self::url::{Quoter, Url}; +pub use self::url::Url; diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs new file mode 100644 index 000000000..26ecc92cd --- /dev/null +++ b/actix-router/src/quoter.rs @@ -0,0 +1,219 @@ +#[allow(dead_code)] +const GEN_DELIMS: &[u8] = b":/?#[]@"; + +#[allow(dead_code)] +const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; + +#[allow(dead_code)] +const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; + +#[allow(dead_code)] +const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; + +#[allow(dead_code)] +const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~"; + +const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~ + !$'()*,"; + +const QS: &[u8] = b"+&=;b"; + +/// A quoter +pub struct Quoter { + /// Simple bit-map of safe values in the 0-127 ASCII range. + safe_table: [u8; 16], + + /// Simple bit-map of protected values in the 0-127 ASCII range. + protected_table: [u8; 16], +} + +impl Quoter { + pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { + let mut quoter = Quoter { + safe_table: [0; 16], + protected_table: [0; 16], + }; + + // prepare safe table + for ch in 0..128 { + if ALLOWED.contains(&ch) { + set_bit(&mut quoter.safe_table, ch); + } + + if QS.contains(&ch) { + set_bit(&mut quoter.safe_table, ch); + } + } + + for &ch in safe { + set_bit(&mut quoter.safe_table, ch) + } + + // prepare protected table + for &ch in protected { + set_bit(&mut quoter.safe_table, ch); + set_bit(&mut quoter.protected_table, ch); + } + + quoter + } + + /// Re-quotes... ? + /// + /// Returns `None` when no modification to the original string was required. + pub fn requote(&self, val: &[u8]) -> Option { + let mut has_pct = 0; + let mut pct = [b'%', 0, 0]; + let mut idx = 0; + let mut cloned: Option> = None; + + let len = val.len(); + + while idx < len { + let ch = val[idx]; + + if has_pct != 0 { + pct[has_pct] = val[idx]; + has_pct += 1; + + if has_pct == 3 { + has_pct = 0; + let buf = cloned.as_mut().unwrap(); + + if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { + if ch < 128 { + if bit_at(&self.protected_table, ch) { + buf.extend_from_slice(&pct); + idx += 1; + continue; + } + + if bit_at(&self.safe_table, ch) { + buf.push(ch); + idx += 1; + continue; + } + } + + buf.push(ch); + } else { + buf.extend_from_slice(&pct[..]); + } + } + } else if ch == b'%' { + has_pct = 1; + + if cloned.is_none() { + let mut c = Vec::with_capacity(len); + c.extend_from_slice(&val[..idx]); + cloned = Some(c); + } + } else if let Some(ref mut cloned) = cloned { + cloned.push(ch) + } + + idx += 1; + } + + cloned.map(|data| String::from_utf8_lossy(&data).into_owned()) + } +} + +/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer +/// representation from `0x0`–`0xF`. +/// +/// - `0x30 ('0') => 0x0` +/// - `0x39 ('9') => 0x9` +/// - `0x41 ('a') => 0xA` +/// - `0x61 ('A') => 0xA` +/// - `0x46 ('f') => 0xF` +/// - `0x66 ('F') => 0xF` +fn from_ascii_hex(v: u8) -> Option { + match v { + b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 + b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 + b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 + _ => None, + } +} + +/// Decode a ASCII hex-encoded pair to an integer. +/// +/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. +/// +/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` +/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` +/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` +fn hex_pair_to_char(d1: u8, d2: u8) -> Option { + let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); + + // left shift high nibble by 4 bits + Some(d_high << 4 | d_low) +} + +/// Sets bit in given bit-map to 1=true. +/// +/// # Panics +/// Panics if `ch` index is out of bounds. +fn set_bit(array: &mut [u8], ch: u8) { + array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) +} + +/// Returns true if bit to true in given bit-map. +/// +/// # Panics +/// Panics if `ch` index is out of bounds. +fn bit_at(array: &[u8], ch: u8) -> bool { + array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_encoding() { + let hex = b"0123456789abcdefABCDEF"; + + for i in 0..256 { + let c = i as u8; + if hex.contains(&c) { + assert!(from_ascii_hex(c).is_some()) + } else { + assert!(from_ascii_hex(c).is_none()) + } + } + + let expected = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, + ]; + for i in 0..hex.len() { + assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); + } + } + + #[test] + fn custom_quoter() { + let q = Quoter::new(b"", b"+"); + assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c"); + assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc"); + + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); + assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); + assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); + assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb"); + } + + #[test] + fn quoter_no_modification() { + let q = Quoter::new(b"", b""); + assert_eq!(q.requote(b"/abc/../efg"), None); + } +} diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index 156c1e1c6..c5a3508aa 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -1,30 +1,6 @@ use crate::ResourcePath; -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; - -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; - -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; - -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; - -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; - -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; - -const QS: &[u8] = b"+&=;b"; +use crate::Quoter; thread_local! { static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); @@ -44,18 +20,20 @@ impl Url { } #[inline] - pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { + pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { Url { path: quoter.requote(uri.path().as_bytes()), uri, } } + /// Returns URI. #[inline] pub fn uri(&self) -> &http::Uri { &self.uri } + /// Returns path. #[inline] pub fn path(&self) -> &str { match self.path { @@ -84,155 +62,6 @@ impl ResourcePath for Url { } } -/// A quoter -pub struct Quoter { - /// Simple bit-map of safe values in the 0-127 ASCII range. - safe_table: [u8; 16], - - /// Simple bit-map of protected values in the 0-127 ASCII range. - protected_table: [u8; 16], -} - -impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut quoter = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for ch in 0..128 { - if ALLOWED.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - - if QS.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - } - - for &ch in safe { - set_bit(&mut quoter.safe_table, ch) - } - - // prepare protected table - for &ch in protected { - set_bit(&mut quoter.safe_table, ch); - set_bit(&mut quoter.protected_table, ch); - } - - quoter - } - - /// Re-quotes... ? - /// - /// Returns `None` when no modification to the original string was required. - pub fn requote(&self, val: &[u8]) -> Option { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } - } - } else if ch == b'%' { - has_pct = 1; - - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) - } - - idx += 1; - } - - cloned.map(|data| String::from_utf8_lossy(&data).into_owned()) - } -} - -/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer -/// representation from `0x0`–`0xF`. -/// -/// - `0x30 ('0') => 0x0` -/// - `0x39 ('9') => 0x9` -/// - `0x41 ('a') => 0xA` -/// - `0x61 ('A') => 0xA` -/// - `0x46 ('f') => 0xF` -/// - `0x66 ('F') => 0xF` -fn from_ascii_hex(v: u8) -> Option { - match v { - b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 - b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 - b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 - _ => None, - } -} - -/// Decode a ASCII hex-encoded pair to an integer. -/// -/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. -/// -/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` -/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` -/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` -fn hex_pair_to_char(d1: u8, d2: u8) -> Option { - let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); - - // left shift high nibble by 4 bits - Some(d_high << 4 | d_low) -} - -/// Sets bit in given bit-map to 1=true. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) -} - -/// Returns true if bit to true in given bit-map. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 -} - #[cfg(test)] mod tests { use http::Uri; @@ -308,44 +137,4 @@ mod tests { // We should always get a valid utf8 string assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok()); } - - #[test] - fn hex_encoding() { - let hex = b"0123456789abcdefABCDEF"; - - for i in 0..256 { - let c = i as u8; - if hex.contains(&c) { - assert!(from_ascii_hex(c).is_some()) - } else { - assert!(from_ascii_hex(c).is_none()) - } - } - - let expected = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, - ]; - for i in 0..hex.len() { - assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); - } - } - - #[test] - fn custom_quoter() { - let q = Quoter::new(b"", b"+"); - assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc"); - - let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); - assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb"); - } - - #[test] - fn quoter_no_modification() { - let q = Quoter::new(b"", b""); - assert_eq!(q.requote(b"/abc/../efg"), None); - } } diff --git a/src/request.rs b/src/request.rs index cbec70a29..e876c3b4d 100644 --- a/src/request.rs +++ b/src/request.rs @@ -122,7 +122,7 @@ impl HttpRequest { /// Returns a reference to the URL parameters container. /// - /// A url parameter is specified in the form `{identifier}`, where the identifier can be used + /// A URL parameter is specified in the form `{identifier}`, where the identifier can be used /// later in a request handler to access the matched value for that parameter. /// /// # Percent Encoding and URL Parameters diff --git a/src/types/path.rs b/src/types/path.rs index 4a694b763..5d52e0e1e 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -138,6 +138,7 @@ where /// enum Folder { /// #[serde(rename = "inbox")] /// Inbox, +/// /// #[serde(rename = "outbox")] /// Outbox, /// } @@ -147,19 +148,17 @@ where /// format!("Selected folder: {:?}!", folder) /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/messages/{folder}") -/// .app_data(PathConfig::default().error_handler(|err, req| { -/// error::InternalError::from_response( -/// err, -/// HttpResponse::Conflict().into(), -/// ) -/// .into() -/// })) -/// .route(web::post().to(index)), -/// ); -/// } +/// let app = App::new().service( +/// web::resource("/messages/{folder}") +/// .app_data(PathConfig::default().error_handler(|err, req| { +/// error::InternalError::from_response( +/// err, +/// HttpResponse::Conflict().into(), +/// ) +/// .into() +/// })) +/// .route(web::post().to(index)), +/// ); /// ``` #[derive(Clone, Default)] pub struct PathConfig { @@ -167,7 +166,7 @@ pub struct PathConfig { } impl PathConfig { - /// Set custom error handler + /// Set custom error handler. pub fn error_handler(mut self, f: F) -> Self where F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static, diff --git a/tests/weird_poll.rs b/tests/weird_poll.rs new file mode 100644 index 000000000..5844ea2c2 --- /dev/null +++ b/tests/weird_poll.rs @@ -0,0 +1,30 @@ +//! Regression test for https://github.com/actix/actix-web/issues/1321 + +// use actix_http::body::{BodyStream, MessageBody}; +// use bytes::Bytes; +// use futures_channel::oneshot; +// use futures_util::{ +// stream::once, +// task::{noop_waker, Context}, +// }; + +// #[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()) +// }))); + +// let waker = noop_waker(); +// let mut cx = Context::from_waker(&waker); + +// 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); +// } From 86df295ee29587a0800c4b59f948b5d0fe046744 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:19:29 +0000 Subject: [PATCH 253/861] fully percent decode path segments when capturing (#2566) --- actix-router/CHANGES.md | 3 + actix-router/src/de.rs | 124 ++++++++++++++++++++++++++-------------- src/types/path.rs | 12 ++++ 3 files changed, 97 insertions(+), 42 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index c85d10e2a..7b8615570 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566] - Minimum supported Rust version (MSRV) is now 1.54. +[#2566]: https://github.com/actix/actix-net/pull/2566 + ## 0.5.0-beta.3 - 2021-12-17 - Minimum supported Rust version (MSRV) is now 1.52. diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index 775c48b8a..ec7b1066a 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -2,7 +2,11 @@ use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::forward_to_deserialize_any; use crate::path::{Path, PathIter}; -use crate::ResourcePath; +use crate::{Quoter, ResourcePath}; + +thread_local! { + static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b""); +} macro_rules! unsupported_type { ($trait_fn:ident, $name:expr) => { @@ -10,16 +14,13 @@ macro_rules! unsupported_type { where V: Visitor<'de>, { - Err(de::value::Error::custom(concat!( - "unsupported type: ", - $name - ))) + Err(de::Error::custom(concat!("unsupported type: ", $name))) } }; } macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + ($trait_fn:ident, $visit_fn:ident, $tp:expr) => { fn $trait_fn(self, visitor: V) -> Result where V: Visitor<'de>, @@ -33,18 +34,39 @@ macro_rules! parse_single_value { .as_str(), )) } else { - let v = self.path[0].parse().map_err(|_| { - de::value::Error::custom(format!( - "can not parse {:?} to a {}", - &self.path[0], $tp - )) + let decoded = FULL_QUOTER + .with(|q| q.requote(self.path[0].as_bytes())) + .unwrap_or_else(|| self.path[0].to_owned()); + + let v = decoded.parse().map_err(|_| { + de::Error::custom(format!("can not parse {:?} to a {}", &self.path[0], $tp)) })?; + visitor.$visit_fn(v) } } }; } +macro_rules! parse_value { + ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let decoded = FULL_QUOTER + .with(|q| q.requote(self.value.as_bytes())) + .unwrap_or_else(|| self.value.to_owned()); + + let v = decoded.parse().map_err(|_| { + de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) + })?; + + visitor.$visit_fn(v) + } + }; +} + pub struct PathDeserializer<'de, T: ResourcePath> { path: &'de Path, } @@ -172,23 +194,6 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> } } - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.path.segment_count() != 1 { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected 1", - self.path.segment_count() - ) - .as_str(), - )) - } else { - visitor.visit_str(&self.path[0]) - } - } - fn deserialize_seq(self, visitor: V) -> Result where V: Visitor<'de>, @@ -215,6 +220,7 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> parse_single_value!(deserialize_u64, visit_u64, "u64"); parse_single_value!(deserialize_f32, visit_f32, "f32"); parse_single_value!(deserialize_f64, visit_f64, "f64"); + parse_single_value!(deserialize_str, visit_string, "String"); parse_single_value!(deserialize_string, visit_string, "String"); parse_single_value!(deserialize_byte_buf, visit_string, "String"); parse_single_value!(deserialize_char, visit_char, "char"); @@ -279,20 +285,6 @@ impl<'de> Deserializer<'de> for Key<'de> { } } -macro_rules! parse_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - let v = self.value.parse().map_err(|_| { - de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) - })?; - visitor.$visit_fn(v) - } - }; -} - struct Value<'de> { value: &'de str, } @@ -497,6 +489,7 @@ mod tests { use super::*; use crate::path::Path; use crate::router::Router; + use crate::ResourceDef; #[derive(Deserialize)] struct MyStruct { @@ -657,6 +650,53 @@ mod tests { assert!(format!("{:?}", s).contains("can not parse")); } + #[test] + fn deserialize_path_decode_string() { + let rdef = ResourceDef::new("/{key}"); + + let mut path = Path::new("/%25"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let segment: String = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(segment, "%"); + + let mut path = Path::new("/%2F"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let segment: String = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(segment, "/") + } + + #[test] + fn deserialize_path_decode_seq() { + let rdef = ResourceDef::new("/{key}/{value}"); + + let mut path = Path::new("/%25/%2F"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(segment.0, "%"); + assert_eq!(segment.1, "/"); + } + + #[test] + fn deserialize_path_decode_map() { + #[derive(Deserialize)] + struct Vals { + key: String, + value: String, + } + + let rdef = ResourceDef::new("/{key}/{value}"); + + let mut path = Path::new("/%25/%2F"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let vals: Vals = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(vals.key, "%"); + assert_eq!(vals.value, "/"); + } + // #[test] // fn test_extract_path_decode() { // let mut router = Router::<()>::default(); diff --git a/src/types/path.rs b/src/types/path.rs index 5d52e0e1e..c3efc22c0 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -285,6 +285,18 @@ mod tests { assert_eq!(res[1], "32".to_owned()); } + #[actix_rt::test] + async fn paths_decoded() { + let resource = ResourceDef::new("/{key}/{value}"); + let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%251").to_srv_request(); + resource.capture_match_info(req.match_info_mut()); + + let (req, mut pl) = req.into_parts(); + let path_items = Path::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(path_items.key, "na+me"); + assert_eq!(path_items.value, "us/er%1"); + } + #[actix_rt::test] async fn test_custom_err_handler() { let (req, mut pl) = TestRequest::with_uri("/name/user1/") From 05336269f98045703b27a0fbc18fe84f06456125 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:33:44 +0000 Subject: [PATCH 254/861] prepare actix-router release 0.5.0-beta.4 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 4 ++-- docs/graphs/web-only.dot | 1 + src/types/json.rs | 4 ++-- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5c4abe46f..cfb7cc0ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = "3.0.0-beta.17" -actix-router = "0.5.0-beta.3" +actix-router = "0.5.0-beta.4" actix-web-codegen = "0.5.0-beta.6" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 7b8615570..66b5379fc 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-beta.4 - 2022-01-04 - `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566] - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index c63448bc7..801613568 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-beta.3" +version = "0.5.0-beta.4" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 03ff4698f..cd463cef9 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -15,10 +15,10 @@ edition = "2018" proc-macro = true [dependencies] +actix-router = "0.5.0-beta.4" +proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "parsing"] } -proc-macro2 = "1" -actix-router = "0.5.0-beta.3" [dev-dependencies] actix-macros = "0.2.3" diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index ee74c292b..b27dd0943 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -15,6 +15,7 @@ digraph { "actix-web" -> { "actix-web-codegen" "actix-http" "actix-router" } "awc" -> { "actix-http" } + "actix-web-codegen" -> { "actix-router" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } "actix-files" -> { "actix-web" } diff --git a/src/types/json.rs b/src/types/json.rs index be6078b2b..8fdbfafa4 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -11,7 +11,7 @@ use std::{ }; use bytes::BytesMut; -use futures_core::{ready, stream::Stream as _}; +use futures_core::{ready, Stream as _}; use serde::{de::DeserializeOwned, Serialize}; use actix_http::Payload; @@ -515,7 +515,7 @@ mod tests { .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; - let resp = HttpResponse::from_error(s.err().unwrap()); + let resp = HttpResponse::from_error(s.unwrap_err()); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let body = body::to_bytes(resp.into_body()).await.unwrap(); From 5abd1c2c2c49379e98dc801124523944038c25e4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:34:16 +0000 Subject: [PATCH 255/861] prepare actix-web-codegen release 0.5.0-rc.1 --- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cfb7cc0ac..4d91b6a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = "3.0.0-beta.17" actix-router = "0.5.0-beta.4" -actix-web-codegen = "0.5.0-beta.6" +actix-web-codegen = "0.5.0-rc.1" ahash = "0.7" bytes = "1" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 5f8c0f259..c044ff74d 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-rc.1 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index cd463cef9..f0f29cc74 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-beta.6" +version = "0.5.0-rc.1" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index abb638cee..1fd97184c 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.6)](https://docs.rs/actix-web-codegen/0.5.0-beta.6) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-rc.1)](https://docs.rs/actix-web-codegen/0.5.0-rc.1) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From b338eb84736d766d5d62bf38e702877dc0f83e1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:34:52 +0000 Subject: [PATCH 256/861] prepare actix-http release 3.0.0-beta.18 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4d91b6a8a..464929968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" actix-router = "0.5.0-beta.4" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 745e3afee..f5a469703 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-beta.18", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index d973ce151..29d3e323b 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 621b42450..935e35561 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.18 - 2022-01-04 ### Added - `impl Eq` for `header::ContentEncoding`. [#2501] - `impl Copy` for `QualityItem` where `T: Copy`. [#2501] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7c9b28944..e18614f1f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.17" +version = "3.0.0-beta.18" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] diff --git a/actix-http/README.md b/actix-http/README.md index 084753ac9..9883cc3f0 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.17)](https://docs.rs/actix-http/3.0.0-beta.17) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.18)](https://docs.rs/actix-http/3.0.0-beta.18) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.17) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.18) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 715512111..5a8c3708e 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index e7ac92e2e..b32fcee1f 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" actix-http-test = "3.0.0-beta.10" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 52ffca1ba..0252c1de0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" actix-web = { version = "4.0.0-beta.18", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0650b5508..d6b6f56c1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.17" +actix-http = "3.0.0-beta.18" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.17", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } From 8621ae12f8b5dbd14f3ac00056b364e065c817e3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:35:08 +0000 Subject: [PATCH 257/861] prepare actix-web release 4.0.0-beta.19 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9e5acce5d..e39eaef61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.19 - 2022-01-04 ### Added - `impl Hash` for `http::header::Encoding`. [#2501] - `AcceptEncoding::negotiate()`. [#2501] diff --git a/Cargo.toml b/Cargo.toml index 464929968..05b4379ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.18" +version = "4.0.0-beta.19" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index 3072ba1c0..b6ee79e3e 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.18)](https://docs.rs/actix-web/4.0.0-beta.18) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.19)](https://docs.rs/actix-web/4.0.0-beta.19) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.18) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.19)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f5a469703..8f8cdbce5 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.18" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.18", default-features = false } +actix-web = { version = "4.0.0-beta.19", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -actix-web = "4.0.0-beta.18" +actix-web = "4.0.0-beta.19" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 29d3e323b..6312b0573 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.18" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e18614f1f..f458eacf5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.18" +actix-web = "4.0.0-beta.19" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 5a8c3708e..b05dd82dc 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.18", default-features = false } +actix-web = { version = "4.0.0-beta.19", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index b32fcee1f..573da3e47 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.10" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.18", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.17", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0252c1de0..e71684a3b 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.18" -actix-web = { version = "4.0.0-beta.18", default-features = false } +actix-web = { version = "4.0.0-beta.19", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index f0f29cc74..b49fe099f 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.10" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.18" +actix-web = "4.0.0-beta.19" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d6b6f56c1..e203988df 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.18", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.19", features = ["openssl"] } brotli2 = "0.3.2" const-str = "0.3" From f659098d219927d3f33264dbfca92d584abc2af9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:35:21 +0000 Subject: [PATCH 258/861] prepare awc release 3.0.0-beta.18 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05b4379ab..2ac6e5f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.12" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.17", features = ["openssl"] } +awc = { version = "3.0.0-beta.18", features = ["openssl"] } brotli2 = "0.3.2" const-str = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6312b0573..1a7520539 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2.0.0-rc.2" -awc = { version = "3.0.0-beta.17", default-features = false } +awc = { version = "3.0.0-beta.18", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 573da3e47..c3a4bb730 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.17", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index e71684a3b..3dd731f43 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.10" -awc = { version = "3.0.0-beta.17", default-features = false } +awc = { version = "3.0.0-beta.18", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 346e95af4..3db649586 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.18 - 2022-01-04 +- No significant changes since `3.0.0-beta.17`. + + ## 3.0.0-beta.17 - 2021-12-29 ### Changed - Update `cookie` dependency (re-exported) to `0.16`. [#2555] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e203988df..9ac7be0d4 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.17" +version = "3.0.0-beta.18" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index ace2b2eb5..6a68ac05a 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.17)](https://docs.rs/awc/3.0.0-beta.17) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.18)](https://docs.rs/awc/3.0.0-beta.18) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.17/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.17) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.18/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.18) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From bcc8d5c4413297ed7783483b59bf58fc34af9db6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:36:56 +0000 Subject: [PATCH 259/861] prepare actix-multipart release 0.4.0-beta.12 --- Cargo.toml | 2 +- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- actix-multipart/CHANGES.md | 3 +++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- awc/CHANGES.md | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ac6e5f06..28c822573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0-beta.12" +actix-files = "0.6.0-beta.13" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.18", features = ["openssl"] } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 93a122941..e8a07d884 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.13 - 2022-01-04 - The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398] - The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398] - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8f8cdbce5..a83dd399d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.12" +version = "0.6.0-beta.13" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index 41dd714d3..be878d958 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.12)](https://docs.rs/actix-files/0.6.0-beta.12) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.13)](https://docs.rs/actix-files/0.6.0-beta.13) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.12/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.12) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.13/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.13) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 65fe51d44..92feade3b 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.4.0-beta.12 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index b05dd82dc..4f41caf44 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.11" +version = "0.4.0-beta.12" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index a773f5d52..91cd8a6e9 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.11)](https://docs.rs/actix-multipart/0.4.0-beta.11) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.12)](https://docs.rs/actix-multipart/0.4.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.11/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.11) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.12/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.12) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3db649586..f2c81ef25 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,7 +4,7 @@ ## 3.0.0-beta.18 - 2022-01-04 -- No significant changes since `3.0.0-beta.17`. +- Minimum supported Rust version (MSRV) is now 1.54. ## 3.0.0-beta.17 - 2021-12-29 From 742ad56d30b965a9b00040597e72cc874c3b336e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:37:14 +0000 Subject: [PATCH 260/861] prepare actix-web-actors release 4.0.0-beta.10 --- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 5c8091fbb..74ab3c785 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.10 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3dd731f43..f9abf3a0f 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.9" +version = "4.0.0-beta.10" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 0bd007e6a..60e6a9bd9 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web-actors/4.0.0-beta.9) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.10)](https://docs.rs/actix-web-actors/4.0.0-beta.10) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.9) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8c975bcc1f0631e4986146b370bb1f6221a9926d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:37:33 +0000 Subject: [PATCH 261/861] prepare actix-http-test release 3.0.0-beta.11 --- actix-http-test/CHANGES.md | 3 +++ actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index e83e95bd3..b62281798 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.11 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 1a7520539..db92f1983 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.10" +version = "3.0.0-beta.11" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index e2cdc0ba2..10c04b368 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http-test/3.0.0-beta.10) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.11)](https://docs.rs/actix-http-test/3.0.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.10) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.11) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f458eacf5..b0aa199b9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -79,7 +79,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } actix-web = "4.0.0-beta.19" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index c3a4bb730..39fb43b3e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" actix-http = "3.0.0-beta.18" -actix-http-test = "3.0.0-beta.10" +actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9ac7be0d4..339391e09 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } From 8bbf2b505295b9729b9894e5612deff6520ddf9a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 4 Jan 2022 15:37:48 +0000 Subject: [PATCH 262/861] prepare actix-test release 0.1.0-beta.11 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28c822573..130307827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.13" -actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.18", features = ["openssl"] } brotli2 = "0.3.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index a83dd399d..fe780f5ab 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,6 +43,6 @@ tokio-uring = { version = "0.1", optional = true } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.10" +actix-test = "0.1.0-beta.11" actix-web = "4.0.0-beta.19" tempfile = "3.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 9838c6f5f..32ab2344f 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.11 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 39fb43b3e..89f28a5da 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.10" +version = "0.1.0-beta.11" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index f9abf3a0f..25b6ea538 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.10" +actix-test = "0.1.0-beta.11" awc = { version = "3.0.0-beta.18", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index b49fe099f..9b1887012 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ syn = { version = "1", features = ["full", "parsing"] } [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.10" +actix-test = "0.1.0-beta.11" actix-utils = "3.0.0" actix-web = "4.0.0-beta.19" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 339391e09..036c74da9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2.0.0-rc.2" -actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.19", features = ["openssl"] } From 0f7292c69a5a229c6d06536682001f7f4ea34ff7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 5 Jan 2022 04:24:40 +0000 Subject: [PATCH 263/861] remove readme msrv link --- README.md | 4 ++-- scripts/bump | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b6ee79e3e..5c5a55743 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.19)](https://docs.rs/actix-web/4.0.0-beta.19) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.19)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) +[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) ![downloads](https://img.shields.io/crates/d/actix-web.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/scripts/bump b/scripts/bump index 1cd190e03..c43b92dc8 100755 --- a/scripts/bump +++ b/scripts/bump @@ -17,9 +17,18 @@ if [ "$(uname)" = "Darwin" ]; then fi CARGO_MANIFEST=$DIR/Cargo.toml -CHANGELOG_FILE=$DIR/CHANGES.md README_FILE=$DIR/README.md +# determine changelog file name +if [ -f "$DIR/CHANGES.md" ]; then + CHANGELOG_FILE=$DIR/CHANGES.md +elif [ -f "$DIR/CHANGELOG.md" ]; then + CHANGELOG_FILE=$DIR/CHANGELOG.md +else + echo "No changelog file found" + exit 1 +fi + # get current version PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")" From 49cfabeaf51a795ee49fc058bd05528225ad5101 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 07:34:13 +0300 Subject: [PATCH 264/861] simplify Resource trait (#2568) Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 4 ++++ actix-router/src/path.rs | 36 ++++++++++++++++++++++++++++--- actix-router/src/resource.rs | 5 ++--- actix-router/src/resource_path.rs | 7 ++++-- actix-router/src/router.rs | 22 ++++++++----------- src/service.rs | 6 ++++-- 6 files changed, 57 insertions(+), 23 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 66b5379fc..3034ed794 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568] +- `Resource` is now implemented for `&mut Path<_>` and `RefMut>`. [#2568] + +[#2568]: https://github.com/actix/actix-web/pull/2568 ## 0.5.0-beta.4 - 2022-01-04 diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index 9af7b0b8b..fc7bb16ac 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use std::ops::Index; +use std::ops::{DerefMut, Index}; use firestorm::profile_method; use serde::de; @@ -213,8 +213,38 @@ impl Index for Path { } } -impl Resource for Path { - fn resource_path(&mut self) -> &mut Self { +impl Resource for Path { + type Path = T; + + fn resource_path(&mut self) -> &mut Path { self } } + +impl Resource for T +where + T: DerefMut>, + P: ResourcePath, +{ + type Path = P; + + fn resource_path(&mut self) -> &mut Path { + &mut *self + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use super::*; + + #[test] + fn deref_impls() { + let mut foo = Path::new("/foo"); + let _ = (&mut foo).resource_path(); + + let foo = RefCell::new(foo); + let _ = foo.borrow_mut().resource_path(); + } +} diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index f1eb9caf5..d39a6b923 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -678,15 +678,14 @@ impl ResourceDef { /// assert!(!try_match(&resource, &mut path)); /// assert_eq!(path.unprocessed(), "/user/admin/stars"); /// ``` - pub fn capture_match_info_fn( + pub fn capture_match_info_fn( &self, resource: &mut R, check_fn: F, user_data: U, ) -> bool where - R: Resource, - T: ResourcePath, + R: Resource, F: FnOnce(&R, U) -> bool, { profile_method!(capture_match_info_fn); diff --git a/actix-router/src/resource_path.rs b/actix-router/src/resource_path.rs index 91a7f2f55..00eb0927c 100644 --- a/actix-router/src/resource_path.rs +++ b/actix-router/src/resource_path.rs @@ -2,8 +2,11 @@ use crate::Path; // TODO: this trait is necessary, document it // see impl Resource for ServiceRequest -pub trait Resource { - fn resource_path(&mut self) -> &mut Path; +pub trait Resource { + /// Type of resource's path returned in `resource_path`. + type Path: ResourcePath; + + fn resource_path(&mut self) -> &mut Path; } pub trait ResourcePath { diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index fad1a440b..47940708e 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -1,6 +1,6 @@ use firestorm::profile_method; -use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath}; +use crate::{IntoPatterns, Resource, ResourceDef}; #[derive(Debug, Copy, Clone, PartialEq)] pub struct ResourceId(pub u16); @@ -26,10 +26,9 @@ impl Router { } } - pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> + pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> where - R: Resource

, - P: ResourcePath, + R: Resource, { profile_method!(recognize); @@ -42,10 +41,9 @@ impl Router { None } - pub fn recognize_mut(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> + pub fn recognize_mut(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> where - R: Resource

, - P: ResourcePath, + R: Resource, { profile_method!(recognize_mut); @@ -58,11 +56,10 @@ impl Router { None } - pub fn recognize_fn(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> + pub fn recognize_fn(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> where F: Fn(&R, &Option) -> bool, - R: Resource

, - P: ResourcePath, + R: Resource, { profile_method!(recognize_checked); @@ -75,15 +72,14 @@ impl Router { None } - pub fn recognize_mut_fn( + pub fn recognize_mut_fn( &mut self, resource: &mut R, check: F, ) -> Option<(&mut T, ResourceId)> where F: Fn(&R, &Option) -> bool, - R: Resource

, - P: ResourcePath, + R: Resource, { profile_method!(recognize_mut_checked); diff --git a/src/service.rs b/src/service.rs index 975556197..f15cbfc9f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -307,9 +307,11 @@ impl ServiceRequest { } } -impl Resource for ServiceRequest { +impl Resource for ServiceRequest { + type Path = Url; + #[inline] - fn resource_path(&mut self) -> &mut Path { + fn resource_path(&mut self) -> &mut Path { self.match_info_mut() } } From 2462b6dd5d2162f8bbe26c3cbccb351e6497d31e Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 07:42:52 +0300 Subject: [PATCH 265/861] generalize impl Responder for HttpResponse (#2567) Co-authored-by: Rob Ede --- CHANGES.md | 4 ++++ Cargo.toml | 1 + src/response/builder.rs | 11 ++++++++++- src/response/customize_responder.rs | 8 ++------ src/response/responder.rs | 22 +--------------------- src/response/response.rs | 21 ++++++++++++++++++++- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e39eaef61..922e0c7c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- `HttpResponse` can now be used as a `Responder` with any body type. + +[#2501]: https://github.com/actix/actix-web/pull/2501 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/Cargo.toml b/Cargo.toml index 130307827..a2b8fb885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"] rand = "0.8" rcgen = "0.8" rustls-pemfile = "0.2" +static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } zstd = "0.9" diff --git a/src/response/builder.rs b/src/response/builder.rs index 93d8ab567..bdb0aaa12 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -23,7 +23,7 @@ use cookie::{Cookie, CookieJar}; use crate::{ error::{Error, JsonPayloadError}, - BoxError, HttpResponse, + BoxError, HttpRequest, HttpResponse, Responder, }; /// An HTTP response builder. @@ -424,6 +424,15 @@ impl Future for HttpResponseBuilder { } } +impl Responder for HttpResponseBuilder { + type Body = BoxBody; + + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + self.finish() + } +} + #[cfg(test)] mod tests { use actix_http::body; diff --git a/src/response/customize_responder.rs b/src/response/customize_responder.rs index 11f6b2916..8cb146dda 100644 --- a/src/response/customize_responder.rs +++ b/src/response/customize_responder.rs @@ -1,12 +1,9 @@ use actix_http::{ - body::{EitherBody, MessageBody}, - error::HttpError, - header::HeaderMap, - header::TryIntoHeaderPair, + body::EitherBody, error::HttpError, header::HeaderMap, header::TryIntoHeaderPair, StatusCode, }; -use crate::{BoxError, HttpRequest, HttpResponse, Responder}; +use crate::{HttpRequest, HttpResponse, Responder}; /// Allows overriding status code and headers for a [`Responder`]. /// @@ -143,7 +140,6 @@ impl CustomizeResponder { impl Responder for CustomizeResponder where T: Responder, - ::Error: Into, { type Body = EitherBody; diff --git a/src/response/responder.rs b/src/response/responder.rs index 319b824f1..d1b9e49e0 100644 --- a/src/response/responder.rs +++ b/src/response/responder.rs @@ -7,7 +7,7 @@ use actix_http::{ }; use bytes::{Bytes, BytesMut}; -use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; +use crate::{Error, HttpRequest, HttpResponse}; use super::CustomizeResponder; @@ -57,15 +57,6 @@ pub trait Responder { } } -impl Responder for HttpResponse { - type Body = BoxBody; - - #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - self - } -} - impl Responder for actix_http::Response { type Body = BoxBody; @@ -75,15 +66,6 @@ impl Responder for actix_http::Response { } } -impl Responder for HttpResponseBuilder { - type Body = BoxBody; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { - self.finish() - } -} - impl Responder for actix_http::ResponseBuilder { type Body = BoxBody; @@ -96,7 +78,6 @@ impl Responder for actix_http::ResponseBuilder { impl Responder for Option where T: Responder, - ::Error: Into, { type Body = EitherBody; @@ -111,7 +92,6 @@ where impl Responder for Result where T: Responder, - ::Error: Into, E: Into, { type Body = EitherBody; diff --git a/src/response/response.rs b/src/response/response.rs index 6fa2082e7..f24a75b19 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -22,7 +22,7 @@ use { cookie::Cookie, }; -use crate::{error::Error, HttpResponseBuilder}; +use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder}; /// An outgoing response. pub struct HttpResponse { @@ -311,6 +311,18 @@ impl Future for HttpResponse { } } +impl Responder for HttpResponse +where + B: MessageBody + 'static, +{ + type Body = B; + + #[inline] + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + self + } +} + #[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: std::slice::Iter<'a, HeaderValue>, @@ -333,9 +345,16 @@ impl<'a> Iterator for CookieIter<'a> { #[cfg(test)] mod tests { + use static_assertions::assert_impl_all; + use super::*; use crate::http::header::{HeaderValue, COOKIE}; + assert_impl_all!(HttpResponse: Responder); + assert_impl_all!(HttpResponse: Responder); + assert_impl_all!(HttpResponse<&'static str>: Responder); + assert_impl_all!(HttpResponse: Responder); + #[test] fn test_debug() { let resp = HttpResponse::Ok() From fe0bbfb3da03a29ee7b715334531f4b770496e2d Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 13:48:20 +0300 Subject: [PATCH 266/861] optimize PathDeserializer (#2570) --- actix-router/src/de.rs | 122 +++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 40 deletions(-) diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index ec7b1066a..27aa49ef2 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::forward_to_deserialize_any; @@ -20,7 +22,7 @@ macro_rules! unsupported_type { } macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:expr) => { + ($trait_fn:ident) => { fn $trait_fn(self, visitor: V) -> Result where V: Visitor<'de>, @@ -34,15 +36,10 @@ macro_rules! parse_single_value { .as_str(), )) } else { - let decoded = FULL_QUOTER - .with(|q| q.requote(self.path[0].as_bytes())) - .unwrap_or_else(|| self.path[0].to_owned()); - - let v = decoded.parse().map_err(|_| { - de::Error::custom(format!("can not parse {:?} to a {}", &self.path[0], $tp)) - })?; - - visitor.$visit_fn(v) + Value { + value: &self.path[0], + } + .$trait_fn(visitor) } } }; @@ -56,7 +53,8 @@ macro_rules! parse_value { { let decoded = FULL_QUOTER .with(|q| q.requote(self.value.as_bytes())) - .unwrap_or_else(|| self.value.to_owned()); + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed(self.value)); let v = decoded.parse().map_err(|_| { de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) @@ -204,26 +202,26 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> } unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); unsupported_type!(deserialize_option, "Option"); unsupported_type!(deserialize_identifier, "identifier"); unsupported_type!(deserialize_ignored_any, "ignored_any"); - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_str, visit_string, "String"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); + parse_single_value!(deserialize_bool); + parse_single_value!(deserialize_i8); + parse_single_value!(deserialize_i16); + parse_single_value!(deserialize_i32); + parse_single_value!(deserialize_i64); + parse_single_value!(deserialize_u8); + parse_single_value!(deserialize_u16); + parse_single_value!(deserialize_u32); + parse_single_value!(deserialize_u64); + parse_single_value!(deserialize_f32); + parse_single_value!(deserialize_f64); + parse_single_value!(deserialize_str); + parse_single_value!(deserialize_string); + parse_single_value!(deserialize_bytes); + parse_single_value!(deserialize_byte_buf); + parse_single_value!(deserialize_char); } struct ParamsDeserializer<'de, T: ResourcePath> { @@ -303,8 +301,6 @@ impl<'de> Deserializer<'de> for Value<'de> { parse_value!(deserialize_u64, visit_u64, "u64"); parse_value!(deserialize_f32, visit_f32, "f32"); parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); parse_value!(deserialize_char, visit_char, "char"); fn deserialize_ignored_any(self, visitor: V) -> Result @@ -332,18 +328,38 @@ impl<'de> Deserializer<'de> for Value<'de> { visitor.visit_unit() } - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - fn deserialize_str(self, visitor: V) -> Result where V: Visitor<'de>, { - visitor.visit_borrowed_str(self.value) + match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + Some(s) => visitor.visit_string(s), + None => visitor.visit_borrowed_str(self.value), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + Some(s) => visitor.visit_byte_buf(s.into()), + None => visitor.visit_borrowed_bytes(self.value.as_bytes()), + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) } fn deserialize_option(self, visitor: V) -> Result @@ -671,12 +687,12 @@ mod tests { fn deserialize_path_decode_seq() { let rdef = ResourceDef::new("/{key}/{value}"); - let mut path = Path::new("/%25/%2F"); + let mut path = Path::new("/%30%25/%30%2F"); rdef.capture_match_info(&mut path); let de = PathDeserializer::new(&path); let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap(); - assert_eq!(segment.0, "%"); - assert_eq!(segment.1, "/"); + assert_eq!(segment.0, "0%"); + assert_eq!(segment.1, "0/"); } #[test] @@ -697,6 +713,32 @@ mod tests { assert_eq!(vals.value, "/"); } + #[test] + fn deserialize_borrowed() { + #[derive(Debug, Deserialize)] + struct Params<'a> { + val: &'a str, + } + + let rdef = ResourceDef::new("/{val}"); + + let mut path = Path::new("/X"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(params.val, "X"); + let de = PathDeserializer::new(&path); + let params: &str = serde::Deserialize::deserialize(de).unwrap(); + assert_eq!(params, "X"); + + let mut path = Path::new("/%2F"); + rdef.capture_match_info(&mut path); + let de = PathDeserializer::new(&path); + assert!( as serde::Deserialize>::deserialize(de).is_err()); + let de = PathDeserializer::new(&path); + assert!(<&str as serde::Deserialize>::deserialize(de).is_err()); + } + // #[test] // fn test_extract_path_decode() { // let mut router = Router::<()>::default(); From 4ebf16890d2e3e45dd9f8352d5ddd712eafa6c47 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 5 Jan 2022 11:47:14 +0000 Subject: [PATCH 267/861] add `GuardContext::header` (#2569) --- CHANGES.md | 10 +++++++--- src/guard.rs | 22 +++++++++++++++++++++- src/http/header/accept.rs | 4 ++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 922e0c7c8..ec09de2ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx -### Changed -- `HttpResponse` can now be used as a `Responder` with any body type. +### Added +- `GuardContext::header` [#2569] -[#2501]: https://github.com/actix/actix-web/pull/2501 +### Changed +- `HttpResponse` can now be used as a `Responder` with any body type. [#2567] + +[#2567]: https://github.com/actix/actix-web/pull/2567 +[#2569]: https://github.com/actix/actix-web/pull/2569 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/src/guard.rs b/src/guard.rs index 7a015d2da..f4200a382 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -54,7 +54,7 @@ use std::{ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead}; -use crate::service::ServiceRequest; +use crate::{http::header::Header, service::ServiceRequest}; /// Provides access to request parts that are useful during routing. #[derive(Debug)] @@ -80,6 +80,26 @@ impl<'a> GuardContext<'a> { pub fn req_data_mut(&self) -> RefMut<'a, Extensions> { self.req.req_data_mut() } + + /// Extracts a typed header from the request. + /// + /// Returns `None` if parsing `H` fails. + /// + /// # Examples + /// ``` + /// use actix_web::{guard::fn_guard, http::header}; + /// + /// let image_accept_guard = fn_guard(|ctx| { + /// match ctx.header::() { + /// Some(hdr) => hdr.preference() == "image/*", + /// None => false, + /// } + /// }); + /// ``` + #[inline] + pub fn header(&self) -> Option { + H::parse(self.req).ok() + } } /// Interface for routing guards. diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 368a05bb2..744c9b6e8 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -2,10 +2,10 @@ use std::cmp::Ordering; use mime::Mime; -use super::QualityItem; +use super::{common_header, QualityItem}; use crate::http::header; -crate::http::header::common_header! { +common_header! { /// `Accept` header, defined /// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) /// From 2d11ab5977ad1394db01c76a79f94c17628a84a7 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 5 Jan 2022 13:31:39 +0100 Subject: [PATCH 268/861] Add `ServiceConfig::configure` (#1988) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/config.rs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ec09de2ac..c379c1545 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,10 +3,12 @@ ## Unreleased - 2021-xx-xx ### Added - `GuardContext::header` [#2569] +- `ServiceConfig::configure` to allow easy nesting of configuration functions. [#1988] ### Changed - `HttpResponse` can now be used as a `Responder` with any body type. [#2567] +[#1988]: https://github.com/actix/actix-web/pull/1988 [#2567]: https://github.com/actix/actix-web/pull/2567 [#2569]: https://github.com/actix/actix-web/pull/2569 diff --git a/src/config.rs b/src/config.rs index d68374387..2482ee6c4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -215,6 +215,17 @@ impl ServiceConfig { self } + /// Run external configuration as part of the application building process + /// + /// Counterpart to [`App::configure()`](crate::App::configure) that allows for easy nesting. + pub fn configure(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut ServiceConfig), + { + f(self); + self + } + /// Configure route for a specific path. /// /// Counterpart to [`App::route()`](crate::App::route). @@ -264,7 +275,7 @@ mod tests { use super::*; use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, read_body, TestRequest}; + use crate::test::{assert_body_eq, call_service, init_service, read_body, TestRequest}; use crate::{web, App, HttpRequest, HttpResponse}; // allow deprecated `ServiceConfig::data` @@ -363,4 +374,22 @@ mod tests { let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn nested_service_configure() { + fn cfg_root(cfg: &mut ServiceConfig) { + cfg.configure(cfg_sub); + } + + fn cfg_sub(cfg: &mut ServiceConfig) { + cfg.route("/", web::get().to(|| async { "hello world" })); + } + + let srv = init_service(App::new().configure(cfg_root)).await; + + let req = TestRequest::with_uri("/").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert_body_eq!(res, b"hello world"); + } } From 4431c8da654f141f564c9715e4d2962d48e0ed69 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 5 Jan 2022 12:18:01 +0000 Subject: [PATCH 269/861] fix bench --- actix-http/benches/quality-value.rs | 17 +++++++++------ src/config.rs | 32 ----------------------------- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs index 31b67f999..33ba9c4c8 100644 --- a/actix-http/benches/quality-value.rs +++ b/actix-http/benches/quality-value.rs @@ -42,32 +42,37 @@ mod _new { if x < 10 { f.write_str("00")?; // 0 is handled so it's not possible to have a trailing 0, we can just return - itoa::fmt(f, x) + itoa_fmt(f, x) } else if x < 100 { f.write_str("0")?; if x % 10 == 0 { // trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } else { // x is in range 101–999 if x % 100 == 0 { // two trailing 0s, divide by 100 and write - itoa::fmt(f, x / 100) + itoa_fmt(f, x / 100) } else if x % 10 == 0 { // one trailing 0, divide by 10 and write - itoa::fmt(f, x / 10) + itoa_fmt(f, x / 10) } else { - itoa::fmt(f, x) + itoa_fmt(f, x) } } } } } } + + pub fn itoa_fmt(mut wr: W, value: V) -> fmt::Result { + let mut buf = itoa::Buffer::new(); + wr.write_str(buf.format(value)) + } } mod _naive { diff --git a/src/config.rs b/src/config.rs index 2482ee6c4..77fba18ed 100644 --- a/src/config.rs +++ b/src/config.rs @@ -299,38 +299,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // #[actix_rt::test] - // async fn test_data_factory() { - // let cfg = |cfg: &mut ServiceConfig| { - // cfg.data_factory(|| { - // sleep(std::time::Duration::from_millis(50)).then(|_| { - // println!("READY"); - // Ok::<_, ()>(10usize) - // }) - // }); - // }; - - // let srv = - // init_service(App::new().configure(cfg).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = srv.call(req).await.unwrap(); - // assert_eq!(resp.status(), StatusCode::OK); - - // let cfg2 = |cfg: &mut ServiceConfig| { - // cfg.data_factory(|| Ok::<_, ()>(10u32)); - // }; - // let srv = init_service( - // App::new() - // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) - // .configure(cfg2), - // ); - // let req = TestRequest::default().to_request(); - // let resp = srv.call(req).await.unwrap(); - // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - // } - #[actix_rt::test] async fn test_external_resource() { let srv = init_service( From c3ce33df0564f6838dcd9cfdcd9a4681145c9322 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 5 Jan 2022 18:02:28 +0300 Subject: [PATCH 270/861] unify generics across App, Scope and Resource (#2572) Co-authored-by: Rob Ede --- src/app.rs | 150 +++++++++++++++++++-------------------- src/middleware/compat.rs | 25 +++++-- src/middleware/mod.rs | 2 +- src/resource.rs | 125 ++++++++++++-------------------- src/scope.rs | 116 ++++++++++++------------------ 5 files changed, 186 insertions(+), 232 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3fddc055b..da33ebc4b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -51,7 +51,10 @@ impl App { } } -impl App { +impl App +where + T: ServiceFactory, +{ /// Set application (root level) data. /// /// Application data stored with `App::app_data()` method is available through the @@ -317,65 +320,63 @@ impl App { self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// life-cycle (request -> response), modifying request/response as - /// necessary, across all requests managed by the *Application*. + /// Registers an app-wide middleware. /// - /// Use middleware when you need to read or modify *every* request or - /// response in some way. + /// Registers middleware, in the form of a middleware compo nen t (type), that runs during + /// inbound and/or outbound processing in the request life-cycle (request -> response), + /// modifying request/response as necessary, across all requests managed by the `App`. /// - /// Notice that the keyword for registering middleware is `wrap`. As you - /// register middleware using `wrap` in the App builder, imagine wrapping - /// layers around an inner App. The first middleware layer exposed to a - /// Request is the outermost layer-- the *last* registered in - /// the builder chain. Consequently, the *first* middleware registered - /// in the builder chain is the *last* to execute during request processing. + /// Use middleware when you need to read or modify *every* request or response in some way. /// + /// Middleware can be applied similarly to individual `Scope`s and `Resource`s. + /// See [`Scope::wrap`](crate::Scope::wrap) and [`Resource::wrap`]. + /// + /// # Middleware Order + /// Notice that the keyword for registering middleware is `wrap`. As you register middleware + /// using `wrap` in the App builder, imagine wrapping layers around an inner App. The first + /// middleware layer exposed to a Request is the outermost layer (i.e., the *last* registered in + /// the builder chain). Consequently, the *first* middleware registered in the builder chain is + /// the *last* to start executing during request processing. + /// + /// Ordering is less obvious when wrapped services also have middleware applied. In this case, + /// middlewares are run in reverse order for `App` _and then_ in reverse order for the + /// wrapped service. + /// + /// # Examples /// ``` - /// use actix_service::Service; /// use actix_web::{middleware, web, App}; - /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .route("/index.html", web::get().to(index)); - /// } + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .route("/index.html", web::get().to(index)); /// ``` - pub fn wrap( + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap( self, mw: M, ) -> App< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, > where - T: ServiceFactory< - ServiceRequest, - Response = ServiceResponse, - Error = Error, - Config = (), - InitError = (), - >, - B: MessageBody, M: Transform< - T::Service, - ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1: MessageBody, + T::Service, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody, { App { endpoint: apply(mw, self.endpoint), @@ -388,61 +389,57 @@ impl App { } } - /// Registers middleware, in the form of a closure, that runs during inbound - /// and/or outbound processing in the request life-cycle (request -> response), - /// modifying request/response as necessary, across all requests managed by - /// the *Application*. + /// Registers an app-wide function middleware. + /// + /// `mw` is a closure that runs during inbound and/or outbound processing in the request + /// life-cycle (request -> response), modifying request/response as necessary, across all + /// requests handled by the `App`. /// /// Use middleware when you need to read or modify *every* request or response in some way. /// + /// Middleware can also be applied to individual `Scope`s and `Resource`s. + /// + /// See [`App::wrap`] for details on how middlewares compose with each other. + /// + /// # Examples /// ``` - /// use actix_service::Service; - /// use actix_web::{web, App}; + /// use actix_web::{dev::Service as _, middleware, web, App}; /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new() - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route("/index.html", web::get().to(index)); - /// } + /// let app = App::new() + /// .wrap_fn(|req, srv| { + /// let fut = srv.call(req); + /// async { + /// let mut res = fut.await?; + /// res.headers_mut() + /// .insert(CONTENT_TYPE, HeaderValue::from_static("text/plain")); + /// Ok(res) + /// } + /// }) + /// .route("/index.html", web::get().to(index)); /// ``` - pub fn wrap_fn( + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap_fn( self, mw: F, ) -> App< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, > where - T: ServiceFactory< - ServiceRequest, - Response = ServiceResponse, - Error = Error, - Config = (), - InitError = (), - >, + F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static, + R: Future, Error>>, B: MessageBody, - F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future, Error>>, - B1: MessageBody, { App { endpoint: apply_fn_factory(self.endpoint, mw), @@ -458,15 +455,14 @@ impl App { impl IntoServiceFactory, Request> for App where - B: MessageBody, T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - T::Future: 'static, + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody, { fn into_factory(self) -> AppInit { AppInit { diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 18c9ff6a7..ee8b8a498 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -150,11 +150,13 @@ mod tests { use actix_service::IntoService; - use crate::dev::ServiceRequest; - use crate::http::StatusCode; - use crate::middleware::{self, Condition, Logger}; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::ServiceRequest, + http::StatusCode, + middleware::{self, Condition, Logger}, + test::{self, call_service, init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] #[cfg(all(feature = "cookies", feature = "__compress"))] @@ -219,4 +221,17 @@ mod tests { let resp = call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + + #[actix_rt::test] + async fn compat_noop_is_noop() { + let srv = test::ok_service(); + + let mw = Compat::noop() + .new_transform(srv.into_service()) + .await + .unwrap(); + + let resp = call_service(&mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.status(), StatusCode::OK); + } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index a781052a6..0a61ad6cb 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,4 +1,4 @@ -//! Commonly used middleware. +//! A collection of common middleware. mod compat; mod condition; diff --git a/src/resource.rs b/src/resource.rs index 8da0a8a85..193757eaa 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, rc::Rc}; -use actix_http::{body::BoxBody, Extensions}; +use actix_http::Extensions; use actix_router::{IntoPatterns, Patterns}; use actix_service::{ apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory, @@ -42,7 +42,7 @@ use crate::{ /// /// If no matching route could be found, *405* response code get returned. Default behavior could be /// overridden with `default_resource()` method. -pub struct Resource { +pub struct Resource { endpoint: T, rdef: Patterns, name: Option, @@ -51,7 +51,6 @@ pub struct Resource { guards: Vec>, default: BoxedHttpServiceFactory, factory_ref: Rc>>, - _phantom: PhantomData, } impl Resource { @@ -69,21 +68,13 @@ impl Resource { default: boxed::factory(fn_service(|req: ServiceRequest| async { Ok(req.into_response(HttpResponse::MethodNotAllowed())) })), - _phantom: PhantomData, } } } -impl Resource +impl Resource where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B: MessageBody, + T: ServiceFactory, { /// Set resource name. /// @@ -241,35 +232,35 @@ where self } - /// Register a resource middleware. + /// Registers a resource middleware. /// - /// This is similar to `App's` middlewares, but middleware get invoked on resource level. - /// Resource level middlewares are not allowed to change response - /// type (i.e modify response's body). + /// `mw` is a middleware component (type), that can modify the request and response across all + /// routes managed by this `Resource`. /// - /// **Note**: middlewares get called in opposite order of middlewares registration. - pub fn wrap( + /// See [`App::wrap`](crate::App::wrap) for more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap( self, mw: M, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, - B1, > where M: Transform< - T::Service, - ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1: MessageBody, + T::Service, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody, { Resource { endpoint: apply(mw, self.endpoint), @@ -280,61 +271,34 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, - _phantom: PhantomData, } } - /// Register a resource middleware function. + /// Registers a resource function middleware. /// - /// This function accepts instance of `ServiceRequest` type and - /// mutable reference to the next middleware in chain. + /// `mw` is a closure that runs during inbound and/or outbound processing in the request + /// life-cycle (request -> response), modifying request/response as necessary, across all + /// requests handled by the `Resource`. /// - /// This is similar to `App's` middlewares, but middleware get invoked on resource level. - /// Resource level middlewares are not allowed to change response - /// type (i.e modify response's body). - /// - /// ``` - /// use actix_service::Service; - /// use actix_web::{web, App}; - /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html") - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route(web::get().to(index))); - /// } - /// ``` - pub fn wrap_fn( + /// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap_fn( self, mw: F, ) -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, - B1, > where - F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future, Error>>, - B1: MessageBody, + F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static, + R: Future, Error>>, + B: MessageBody, { Resource { endpoint: apply_fn_factory(self.endpoint, mw), @@ -345,7 +309,6 @@ where default: self.default, app_data: self.app_data, factory_ref: self.factory_ref, - _phantom: PhantomData, } } @@ -373,7 +336,7 @@ where } } -impl HttpServiceFactory for Resource +impl HttpServiceFactory for Resource where T: ServiceFactory< ServiceRequest, @@ -517,7 +480,7 @@ mod tests { header::{self, HeaderValue}, Method, StatusCode, }, - middleware::{Compat, DefaultHeaders}, + middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{call_service, init_service, TestRequest}, web, App, Error, HttpMessage, HttpResponse, @@ -525,31 +488,35 @@ mod tests { #[test] fn can_be_returned_from_fn() { - fn my_resource() -> Resource { - web::resource("/test").route(web::get().to(|| async { "hello" })) + fn my_resource_1() -> Resource { + web::resource("/test1").route(web::get().to(|| async { "hello" })) } - fn my_compat_resource() -> Resource< + fn my_resource_2() -> Resource< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, > { - web::resource("/test-compat") + web::resource("/test2") .wrap_fn(|req, srv| { let fut = srv.call(req); async { Ok(fut.await?.map_into_right_body::<()>()) } }) - .wrap(Compat::noop()) .route(web::get().to(|| async { "hello" })) } + fn my_resource_3() -> impl HttpServiceFactory { + web::resource("/test2").route(web::get().to(|| async { "hello" })) + } + App::new() - .service(my_resource()) - .service(my_compat_resource()); + .service(my_resource_1()) + .service(my_resource_2()) + .service(my_resource_3()); } #[actix_rt::test] diff --git a/src/scope.rs b/src/scope.rs index fa9807f42..c05ce054d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,9 +1,6 @@ -use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, mem, rc::Rc}; +use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; -use actix_http::{ - body::{BoxBody, MessageBody}, - Extensions, -}; +use actix_http::{body::MessageBody, Extensions}; use actix_router::{ResourceDef, Router}; use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, @@ -57,7 +54,7 @@ type Guards = Vec>; /// /// [pat]: crate::dev::ResourceDef#prefix-resources /// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments -pub struct Scope { +pub struct Scope { endpoint: T, rdef: String, app_data: Option, @@ -66,7 +63,6 @@ pub struct Scope { default: Option>, external: Vec, factory_ref: Rc>>, - _phantom: PhantomData, } impl Scope { @@ -83,21 +79,13 @@ impl Scope { default: None, external: Vec::new(), factory_ref, - _phantom: Default::default(), } } } -impl Scope +impl Scope where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B: 'static, + T: ServiceFactory, { /// Add match guard to a scope. /// @@ -296,32 +284,35 @@ where self } - /// Registers middleware, in the form of a middleware component (type), that runs during inbound - /// processing in the request life-cycle (request -> response), modifying request as necessary, - /// across all requests managed by the *Scope*. + /// Registers a scope-wide middleware. /// - /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( + /// `mw` is a middleware component (type), that can modify the request and response across all + /// sub-resources managed by this `Scope`. + /// + /// See [`App::wrap`](crate::App::wrap) for more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap( self, mw: M, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, - B1, > where M: Transform< - T::Service, - ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, + T::Service, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody, { Scope { endpoint: apply(mw, self.endpoint), @@ -332,54 +323,34 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, - _phantom: PhantomData, } } - /// Registers middleware, in the form of a closure, that runs during inbound processing in the - /// request life-cycle (request -> response), modifying request as necessary, across all - /// requests managed by the *Scope*. + /// Registers a scope-wide function middleware. /// - /// # Examples - /// ``` - /// use actix_service::Service; - /// use actix_web::{web, App}; - /// use actix_web::http::header::{CONTENT_TYPE, HeaderValue}; + /// `mw` is a closure that runs during inbound and/or outbound processing in the request + /// life-cycle (request -> response), modifying request/response as necessary, across all + /// requests handled by the `Scope`. /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// let app = App::new().service( - /// web::scope("/app") - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route("/index.html", web::get().to(index))); - /// ``` - pub fn wrap_fn( + /// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap_fn( self, mw: F, ) -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, - B1, > where - F: Fn(ServiceRequest, &T::Service) -> R + Clone, - R: Future, Error>>, + F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static, + R: Future, Error>>, + B: MessageBody, { Scope { endpoint: apply_fn_factory(self.endpoint, mw), @@ -390,12 +361,11 @@ where default: self.default, external: self.external, factory_ref: self.factory_ref, - _phantom: PhantomData, } } } -impl HttpServiceFactory for Scope +impl HttpServiceFactory for Scope where T: ServiceFactory< ServiceRequest, @@ -596,7 +566,7 @@ mod tests { header::{self, HeaderValue}, Method, StatusCode, }, - middleware::{Compat, DefaultHeaders}, + middleware::DefaultHeaders, service::{ServiceRequest, ServiceResponse}, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, web, App, HttpMessage, HttpRequest, HttpResponse, @@ -604,16 +574,16 @@ mod tests { #[test] fn can_be_returned_from_fn() { - fn my_scope() -> Scope { + fn my_scope_1() -> Scope { web::scope("/test") .service(web::resource("").route(web::get().to(|| async { "hello" }))) } - fn my_compat_scope() -> Scope< + fn my_scope_2() -> Scope< impl ServiceFactory< ServiceRequest, Config = (), - Response = ServiceResponse, + Response = ServiceResponse, Error = Error, InitError = (), >, @@ -623,11 +593,17 @@ mod tests { let fut = srv.call(req); async { Ok(fut.await?.map_into_right_body::<()>()) } }) - .wrap(Compat::noop()) .service(web::resource("").route(web::get().to(|| async { "hello" }))) } - App::new().service(my_scope()).service(my_compat_scope()); + fn my_scope_3() -> impl HttpServiceFactory { + my_scope_2() + } + + App::new() + .service(my_scope_1()) + .service(my_scope_2()) + .service(my_scope_3()); } #[actix_rt::test] From 6c97d448b7efec443945dfc7c2363fd7b2b86962 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 12 Jan 2022 17:53:36 +0000 Subject: [PATCH 271/861] update tokio-uring to 0.2 (#2583) --- Cargo.toml | 4 +-- actix-files/CHANGES.md | 3 ++ actix-files/Cargo.toml | 2 +- actix-files/src/chunked.rs | 64 ++--------------------------------- actix-files/src/lib.rs | 4 +-- actix-files/src/named.rs | 41 +++++----------------- actix-files/src/service.rs | 4 +-- actix-files/tests/encoding.rs | 8 ++--- 8 files changed, 25 insertions(+), 105 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2b8fb885..42de9df15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,8 +71,8 @@ experimental-io-uring = ["actix-server/io-uring"] [dependencies] actix-codec = "0.4.1" actix-macros = "0.2.3" -actix-rt = "2.3" -actix-server = "2.0.0-rc.2" +actix-rt = "2.6" +actix-server = "2.0.0-rc.4" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index e8a07d884..c0504fedc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583] + +[#2583]: https://github.com/actix/actix-web/pull/2583 ## 0.6.0-beta.13 - 2022-01-04 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index fe780f5ab..54a4bd47d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -39,7 +39,7 @@ mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" -tokio-uring = { version = "0.1", optional = true } +tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 68221ccc3..3ee2ee072 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -10,6 +10,9 @@ use actix_web::{error::Error, web::Bytes}; use futures_core::{ready, Stream}; use pin_project_lite::pin_project; +#[cfg(feature = "experimental-io-uring")] +use bytes::BytesMut; + use super::named::File; pin_project! { @@ -214,64 +217,3 @@ where } } } - -#[cfg(feature = "experimental-io-uring")] -use bytes_mut::BytesMut; - -// TODO: remove new type and use bytes::BytesMut directly -#[doc(hidden)] -#[cfg(feature = "experimental-io-uring")] -mod bytes_mut { - use std::ops::{Deref, DerefMut}; - - use tokio_uring::buf::{IoBuf, IoBufMut}; - - #[derive(Debug)] - pub struct BytesMut(bytes::BytesMut); - - impl BytesMut { - pub(super) fn new() -> Self { - Self(bytes::BytesMut::new()) - } - } - - impl Deref for BytesMut { - type Target = bytes::BytesMut; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl DerefMut for BytesMut { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - unsafe impl IoBuf for BytesMut { - fn stable_ptr(&self) -> *const u8 { - self.0.as_ptr() - } - - fn bytes_init(&self) -> usize { - self.0.len() - } - - fn bytes_total(&self) -> usize { - self.0.capacity() - } - } - - unsafe impl IoBufMut for BytesMut { - fn stable_mut_ptr(&mut self) -> *mut u8 { - self.0.as_mut_ptr() - } - - unsafe fn set_init(&mut self, init_len: usize) { - if self.len() < init_len { - self.0.set_len(init_len); - } - } - } -} diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index a11aa32c7..af404721c 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -67,8 +67,8 @@ mod tests { time::{Duration, SystemTime}, }; - use actix_service::ServiceFactory; use actix_web::{ + dev::ServiceFactory, guard, http::{ header::{self, ContentDisposition, DispositionParam, DispositionType}, @@ -303,7 +303,7 @@ mod tests { let resp = file.respond_to(&req).await.unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/javascript" + "application/javascript; charset=utf-8" ); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 019730dc6..14495e660 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,15 +1,16 @@ use std::{ - fmt, fs::Metadata, io, path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, }; -use actix_service::{Service, ServiceFactory}; use actix_web::{ body::{self, BoxBody, SizedStream}, - dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, + dev::{ + self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, + ServiceRequest, ServiceResponse, + }, http::{ header::{ self, Charset, ContentDisposition, ContentEncoding, DispositionParam, @@ -37,7 +38,7 @@ bitflags! { impl Default for Flags { fn default() -> Self { - Flags::from_bits_truncate(0b0000_0111) + Flags::from_bits_truncate(0b0000_1111) } } @@ -65,12 +66,12 @@ impl Default for Flags { /// NamedFile::open_async("./static/index.html").await /// } /// ``` -#[derive(Deref, DerefMut)] +#[derive(Debug, Deref, DerefMut)] pub struct NamedFile { - path: PathBuf, #[deref] #[deref_mut] file: File, + path: PathBuf, modified: Option, pub(crate) md: Metadata, pub(crate) flags: Flags, @@ -80,32 +81,6 @@ pub struct NamedFile { pub(crate) encoding: Option, } -impl fmt::Debug for NamedFile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NamedFile") - .field("path", &self.path) - .field( - "file", - #[cfg(feature = "experimental-io-uring")] - { - &"tokio_uring::File" - }, - #[cfg(not(feature = "experimental-io-uring"))] - { - &self.file - }, - ) - .field("modified", &self.modified) - .field("md", &self.md) - .field("flags", &self.flags) - .field("status_code", &self.status_code) - .field("content_type", &self.content_type) - .field("content_disposition", &self.content_disposition) - .field("encoding", &self.encoding) - .finish() - } -} - #[cfg(not(feature = "experimental-io-uring"))] pub(crate) use std::fs::File; #[cfg(feature = "experimental-io-uring")] @@ -627,7 +602,7 @@ impl Service for NamedFileService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - actix_service::always_ready!(); + dev::always_ready!(); fn call(&self, req: ServiceRequest) -> Self::Future { let (req, _) = req.into_parts(); diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 152e1855e..4e8b72311 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -2,7 +2,7 @@ use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc}; use actix_web::{ body::BoxBody, - dev::{Service, ServiceRequest, ServiceResponse}, + dev::{self, Service, ServiceRequest, ServiceResponse}, error::Error, guard::Guard, http::{header, Method}, @@ -98,7 +98,7 @@ impl Service for FilesService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - actix_service::always_ready!(); + dev::always_ready!(); fn call(&self, req: ServiceRequest) -> Self::Future { let is_method_valid = if let Some(guard) = &self.guards { diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index 652a7c12b..080292af5 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -19,12 +19,12 @@ async fn test_utf8_file_contents() { assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers().get(header::CONTENT_TYPE), - Some(&HeaderValue::from_static("text/plain")), + Some(&HeaderValue::from_static("text/plain; charset=utf-8")), ); - // prefer UTF-8 encoding + // disable UTF-8 attribute let srv = - test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true))) + test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))) .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); @@ -33,6 +33,6 @@ async fn test_utf8_file_contents() { assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers().get(header::CONTENT_TYPE), - Some(&HeaderValue::from_static("text/plain; charset=utf-8")), + Some(&HeaderValue::from_static("text/plain")), ); } From 2a12b41456f40b28c1efe0ec6947e8f50ba22006 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 12 Jan 2022 21:31:48 +0300 Subject: [PATCH 272/861] fix support for 12 extractors (#2582) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/extract.rs | 21 +++++++++++---------- src/handler.rs | 49 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c379c1545..805030dfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,10 +7,12 @@ ### Changed - `HttpResponse` can now be used as a `Responder` with any body type. [#2567] +- Maximim number of extractors has changed from 10 to 12. [#2582] [#1988]: https://github.com/actix/actix-web/pull/1988 [#2567]: https://github.com/actix/actix-web/pull/2567 [#2569]: https://github.com/actix/actix-web/pull/2569 +[#2582]: https://github.com/actix/actix-web/pull/2582 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/src/extract.rs b/src/extract.rs index f74a0a54e..de1cdde0c 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -290,16 +290,6 @@ impl FromRequest for Method { } } -#[doc(hidden)] -impl FromRequest for () { - type Error = Infallible; - type Future = Ready>; - - fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - ok(()) - } -} - #[doc(hidden)] #[allow(non_snake_case)] mod tuple_from_req { @@ -388,6 +378,15 @@ mod tuple_from_req { } } + impl FromRequest for () { + type Error = Infallible; + type Future = Ready>; + + fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { + ok(()) + } + } + tuple_from_req! { TupleFromRequest1; A } tuple_from_req! { TupleFromRequest2; A, B } tuple_from_req! { TupleFromRequest3; A, B, C } @@ -398,6 +397,8 @@ mod tuple_from_req { tuple_from_req! { TupleFromRequest8; A, B, C, D, E, F, G, H } tuple_from_req! { TupleFromRequest9; A, B, C, D, E, F, G, H, I } tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J } + tuple_from_req! { TupleFromRequest11; A, B, C, D, E, F, G, H, I, J, K } + tuple_from_req! { TupleFromRequest12; A, B, C, D, E, F, G, H, I, J, K, L } } #[cfg(test)] diff --git a/src/handler.rs b/src/handler.rs index d458e22e1..7eb70ed25 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -12,17 +12,14 @@ use crate::{ /// # What Is A Request Handler /// A request handler has three requirements: /// 1. It is an async function (or a function/closure that returns an appropriate future); -/// 1. The function accepts zero or more parameters that implement [`FromRequest`]; +/// 1. The function parameters (up to 12) implement [`FromRequest`]; /// 1. The async function (or future) resolves to a type that can be converted into an /// [`HttpResponse`] (i.e., it implements the [`Responder`] trait). /// /// # Compiler Errors /// If you get the error `the trait Handler<_> is not implemented`, then your handler does not -/// fulfill one or more of the above requirements. -/// -/// Unfortunately we cannot provide a better compile error message (while keeping the trait's -/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added -/// to Rust. +/// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on +/// implementing [`FromRequest`] and [`Responder`], respectively. /// /// # How Do Handlers Receive Variable Numbers Of Arguments /// Rest assured there is no macro magic here; it's just traits. @@ -62,13 +59,15 @@ use crate::{ /// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the /// bounds of the handler call after argument extraction: /// ```ignore -/// impl Handler<(Arg1, Arg2), R> for Func +/// impl Handler<(Arg1, Arg2)> for Func /// where -/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static, -/// R: Future, -/// R::Output: Responder, +/// Func: Fn(Arg1, Arg2) -> Fut + Clone + 'static, +/// Fut: Future, /// { -/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R { +/// type Output = Fut::Output; +/// type Future = Fut; +/// +/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> Self::Future { /// (self)(arg1, arg2) /// } /// } @@ -76,7 +75,6 @@ use crate::{ /// /// [arity]: https://en.wikipedia.org/wiki/Arity /// [`from_request`]: FromRequest::from_request -/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628 pub trait Handler: Clone + 'static { type Output; type Future: Future; @@ -121,8 +119,9 @@ where /// ``` macro_rules! factory_tuple ({ $($param:ident)* } => { impl Handler<($($param,)*)> for Func - where Func: Fn($($param),*) -> Fut + Clone + 'static, - Fut: Future, + where + Func: Fn($($param),*) -> Fut + Clone + 'static, + Fut: Future, { type Output = Fut::Output; type Future = Fut; @@ -148,3 +147,25 @@ factory_tuple! { A B C D E F G H I } factory_tuple! { A B C D E F G H I J } factory_tuple! { A B C D E F G H I J K } factory_tuple! { A B C D E F G H I J K L } + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_impl_handler(_: impl Handler) {} + + #[test] + fn arg_number() { + async fn handler_min() {} + + #[rustfmt::skip] + #[allow(clippy::too_many_arguments, clippy::just_underscores_and_digits)] + async fn handler_max( + _01: (), _02: (), _03: (), _04: (), _05: (), _06: (), + _07: (), _08: (), _09: (), _10: (), _11: (), _12: (), + ) {} + + assert_impl_handler(handler_min); + assert_impl_handler(handler_max); + } +} From d90c1a23317547701a284f522d63edd4f269a842 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Wed, 12 Jan 2022 21:59:22 +0300 Subject: [PATCH 273/861] convert error in `Result` extractor (#2581) Co-authored-by: Rob Ede --- CHANGES.md | 3 +++ src/extract.rs | 41 +++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 805030dfb..a82d0eddc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,11 +7,14 @@ ### Changed - `HttpResponse` can now be used as a `Responder` with any body type. [#2567] +- `Result` extractor wrapper can now convert error types. [#2581] +- Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581] - Maximim number of extractors has changed from 10 to 12. [#2582] [#1988]: https://github.com/actix/actix-web/pull/1988 [#2567]: https://github.com/actix/actix-web/pull/2567 [#2569]: https://github.com/actix/actix-web/pull/2569 +[#2581]: https://github.com/actix/actix-web/pull/2581 [#2582]: https://github.com/actix/actix-web/pull/2582 diff --git a/src/extract.rs b/src/extract.rs index de1cdde0c..f16c29ca5 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -3,6 +3,7 @@ use std::{ convert::Infallible, future::Future, + marker::PhantomData, pin::Pin, task::{Context, Poll}, }; @@ -124,12 +125,11 @@ pub trait FromRequest: Sized { /// ); /// } /// ``` -impl FromRequest for Option +impl FromRequest for Option where T: FromRequest, - T::Future: 'static, { - type Error = Error; + type Error = Infallible; type Future = FromRequestOptFuture; #[inline] @@ -152,7 +152,7 @@ where Fut: Future>, E: Into, { - type Output = Result, Error>; + type Output = Result, Infallible>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); @@ -211,40 +211,42 @@ where /// ); /// } /// ``` -impl FromRequest for Result +impl FromRequest for Result where - T: FromRequest + 'static, - T::Error: 'static, - T::Future: 'static, + T: FromRequest, + T::Error: Into, { - type Error = Error; - type Future = FromRequestResFuture; + type Error = Infallible; + type Future = FromRequestResFuture; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { FromRequestResFuture { fut: T::from_request(req, payload), + _phantom: PhantomData, } } } pin_project! { - pub struct FromRequestResFuture { + pub struct FromRequestResFuture { #[pin] fut: Fut, + _phantom: PhantomData, } } -impl Future for FromRequestResFuture +impl Future for FromRequestResFuture where - Fut: Future>, + Fut: Future>, + Ei: Into, { - type Output = Result, Error>; + type Output = Result, Infallible>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); let res = ready!(this.fut.poll(cx)); - Poll::Ready(Ok(res)) + Poll::Ready(Ok(res.map_err(Into::into))) } } @@ -481,7 +483,14 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .to_http_parts(); - let r = Result::, Error>::from_request(&req, &mut pl) + struct MyError; + impl From for MyError { + fn from(_: Error) -> Self { + Self + } + } + + let r = Result::, MyError>::from_request(&req, &mut pl) .await .unwrap(); assert!(r.is_err()); From 32742d07155957d9a6a4eed9eae43adede614a16 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 14 Jan 2022 22:45:32 +0300 Subject: [PATCH 274/861] support opaque app in test helpers (#2584) --- CHANGES.md | 2 ++ src/test/test_utils.rs | 47 +++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a82d0eddc..8ab756100 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,12 +10,14 @@ - `Result` extractor wrapper can now convert error types. [#2581] - Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581] - Maximim number of extractors has changed from 10 to 12. [#2582] +- Removed bound `::Error: Debug` in test utility functions in order to support returning opaque apps. [#2584] [#1988]: https://github.com/actix/actix-web/pull/1988 [#2567]: https://github.com/actix/actix-web/pull/2567 [#2569]: https://github.com/actix/actix-web/pull/2569 [#2581]: https://github.com/actix/actix-web/pull/2581 [#2582]: https://github.com/actix/actix-web/pull/2582 +[#2584]: https://github.com/actix/actix-web/pull/2584 ## 4.0.0-beta.19 - 2022-01-04 diff --git a/src/test/test_utils.rs b/src/test/test_utils.rs index 02d4c9bf3..8207ce270 100644 --- a/src/test/test_utils.rs +++ b/src/test/test_utils.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::error::Error as StdError; use actix_http::Request; use actix_service::IntoServiceFactory; @@ -135,7 +135,6 @@ pub async fn call_and_read_body(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody, - B::Error: fmt::Debug, { let res = call_service(app, req).await; read_body(res).await @@ -147,7 +146,6 @@ pub async fn read_response(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody, - B::Error: fmt::Debug, { let res = call_service(app, req).await; read_body(res).await @@ -186,11 +184,11 @@ where pub async fn read_body(res: ServiceResponse) -> Bytes where B: MessageBody, - B::Error: fmt::Debug, { let body = res.into_body(); body::to_bytes(body) .await + .map_err(Into::>::into) .expect("error reading test response body") } @@ -240,7 +238,6 @@ where pub async fn read_body_json(res: ServiceResponse) -> T where B: MessageBody, - B::Error: fmt::Debug, T: DeserializeOwned, { let body = read_body(res).await; @@ -300,7 +297,6 @@ pub async fn call_and_read_body_json(app: &S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody, - B::Error: fmt::Debug, T: DeserializeOwned, { let res = call_service(app, req).await; @@ -313,7 +309,6 @@ pub async fn read_response_json(app: &S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody, - B::Error: fmt::Debug, T: DeserializeOwned, { call_and_read_body_json(app, req).await @@ -325,7 +320,10 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::{http::header, test::TestRequest, web, App, HttpMessage, HttpResponse}; + use crate::{ + dev::ServiceRequest, http::header, test::TestRequest, web, App, HttpMessage, + HttpResponse, + }; #[actix_rt::test] async fn test_request_methods() { @@ -471,4 +469,37 @@ mod tests { assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } + + #[actix_rt::test] + #[allow(dead_code)] + async fn return_opaque_types() { + fn test_app() -> App< + impl ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = crate::Error, + InitError = (), + >, + > { + App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + )) + } + + async fn test_service( + ) -> impl Service, Error = crate::Error> + { + init_service(test_app()).await + } + + async fn compile_test(mut req: Vec) { + let svc = test_service().await; + call_service(&svc, req.pop().unwrap()).await; + call_and_read_body(&svc, req.pop().unwrap()).await; + read_body(call_service(&svc, req.pop().unwrap()).await).await; + let _: String = call_and_read_body_json(&svc, req.pop().unwrap()).await; + let _: String = read_body_json(call_service(&svc, req.pop().unwrap()).await).await; + } + } } From edbb9b047eab4f2897a5488d506ba1f4ef7957a7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 14 Jan 2022 19:59:36 +0000 Subject: [PATCH 275/861] prepare actix-router release 0.5.0-rc.1 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- src/resource.rs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 42de9df15..1323b4ccf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = "3.0.0-beta.18" -actix-router = "0.5.0-beta.4" +actix-router = "0.5.0-rc.1" actix-web-codegen = "0.5.0-rc.1" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 3034ed794..f268ffa9c 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-rc.1 - 2022-01-14 - `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568] - `Resource` is now implemented for `&mut Path<_>` and `RefMut>`. [#2568] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 801613568..56a755ef4 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-beta.4" +version = "0.5.0-rc.1" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/src/resource.rs b/src/resource.rs index 193757eaa..dd7d4b0d5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -510,7 +510,7 @@ mod tests { } fn my_resource_3() -> impl HttpServiceFactory { - web::resource("/test2").route(web::get().to(|| async { "hello" })) + web::resource("/test3").route(web::get().to(|| async { "hello" })) } App::new() From 8faca783fa3084ce0feeca7256abd6508ef52646 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 14 Jan 2022 20:00:26 +0000 Subject: [PATCH 276/861] prepare actix-web release 4.0.0-beta.20 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8ab756100..b0fa81296 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.20 - 2022-01-14 ### Added - `GuardContext::header` [#2569] - `ServiceConfig::configure` to allow easy nesting of configuration functions. [#1988] diff --git a/Cargo.toml b/Cargo.toml index 1323b4ccf..b3b35790d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.19" +version = "4.0.0-beta.20" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/README.md b/README.md index 5c5a55743..0085c1d6d 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.19)](https://docs.rs/actix-web/4.0.0-beta.19) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.20)](https://docs.rs/actix-web/4.0.0-beta.20) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.19) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.20/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.20)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 54a4bd47d..a783a2cb1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.18" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.19", default-features = false } +actix-web = { version = "4.0.0-beta.20", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.11" -actix-web = "4.0.0-beta.19" +actix-web = "4.0.0-beta.20" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index db92f1983..b8521dd0c 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.18" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b0aa199b9..519230aab 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2.0.0-rc.2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.19" +actix-web = "4.0.0-beta.20" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 4f41caf44..03a2bdfe2 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.19", default-features = false } +actix-web = { version = "4.0.0-beta.20", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 89f28a5da..9bd41ed0c 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.19", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 25b6ea538..169665ddf 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.18" -actix-web = { version = "4.0.0-beta.19", default-features = false } +actix-web = { version = "4.0.0-beta.20", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 9b1887012..51ccac27c 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.11" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.19" +actix-web = "4.0.0-beta.20" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 036c74da9..8accf4d0d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.19", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.20", features = ["openssl"] } brotli2 = "0.3.2" const-str = "0.3" From 455d5c460d18743b5550de138ea81f27cef7d934 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 14 Jan 2022 20:01:11 +0000 Subject: [PATCH 277/861] prepare actix-files release 0.6.0-beta.14 --- CHANGES.md | 2 +- Cargo.toml | 2 +- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b0fa81296..fa4feab6e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ - `HttpResponse` can now be used as a `Responder` with any body type. [#2567] - `Result` extractor wrapper can now convert error types. [#2581] - Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581] -- Maximim number of extractors has changed from 10 to 12. [#2582] +- Maximum number of handler extractors has increased to 12. [#2582] - Removed bound `::Error: Debug` in test utility functions in order to support returning opaque apps. [#2584] [#1988]: https://github.com/actix/actix-web/pull/1988 diff --git a/Cargo.toml b/Cargo.toml index b3b35790d..945d72ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0-beta.13" +actix-files = "0.6.0-beta.14" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.18", features = ["openssl"] } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c0504fedc..f37e27518 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.14 - 2022-01-14 - The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583] [#2583]: https://github.com/actix/actix-web/pull/2583 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index a783a2cb1..304cfa9da 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.13" +version = "0.6.0-beta.14" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index be878d958..77dd1677e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.13)](https://docs.rs/actix-files/0.6.0-beta.13) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.14)](https://docs.rs/actix-files/0.6.0-beta.14) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.13/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.13) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.14/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.14) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From e7cae5a95b80ab1c4513a95f723eb35432c4d9af Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 15 Jan 2022 14:03:16 +0000 Subject: [PATCH 278/861] migrate to `brotli` crate (#2538) --- Cargo.toml | 2 +- actix-http/Cargo.toml | 4 ++-- actix-http/src/encoding/decoder.rs | 7 ++----- actix-http/src/encoding/encoder.rs | 23 ++++++++++++++--------- awc/Cargo.toml | 2 +- awc/tests/utils.rs | 14 ++++++++++---- tests/utils.rs | 14 ++++++++++---- 7 files changed, 40 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 945d72ef8..39f2ac32a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ actix-files = "0.6.0-beta.14" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.18", features = ["openssl"] } -brotli2 = "0.3.2" +brotli = "3.3.3" const-str = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 519230aab..df9e11419 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -33,7 +33,7 @@ openssl = ["actix-tls/accept", "actix-tls/openssl"] rustls = ["actix-tls/accept", "actix-tls/rustls"] # enable compression support -compress-brotli = ["brotli2", "__compress"] +compress-brotli = ["brotli", "__compress"] compress-gzip = ["flate2", "__compress"] compress-zstd = ["zstd", "__compress"] @@ -74,7 +74,7 @@ smallvec = "1.6.1" actix-tls = { version = "3.0.0", default-features = false, optional = true } # compression -brotli2 = { version="0.3.2", optional = true } +brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index da4b56c6a..2ed7be899 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -11,9 +11,6 @@ use actix_rt::task::{spawn_blocking, JoinHandle}; use bytes::Bytes; use futures_core::{ready, Stream}; -#[cfg(feature = "compress-brotli")] -use brotli2::write::BrotliDecoder; - #[cfg(feature = "compress-gzip")] use flate2::write::{GzDecoder, ZlibDecoder}; @@ -48,7 +45,7 @@ where let decoder = match encoding { #[cfg(feature = "compress-brotli")] ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new( - BrotliDecoder::new(Writer::new()), + brotli::DecompressorWriter::new(Writer::new(), 8_096), ))), #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( @@ -165,7 +162,7 @@ enum ContentDecoder { #[cfg(feature = "compress-gzip")] Gzip(Box>), #[cfg(feature = "compress-brotli")] - Brotli(Box>), + Brotli(Box>), // We need explicit 'static lifetime here because ZstdDecoder need lifetime // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` #[cfg(feature = "compress-zstd")] diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 2848ad697..9696da6f1 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -14,9 +14,6 @@ use derive_more::Display; use futures_core::ready; use pin_project_lite::pin_project; -#[cfg(feature = "compress-brotli")] -use brotli2::write::BrotliEncoder; - #[cfg(feature = "compress-gzip")] use flate2::write::{GzEncoder, ZlibEncoder}; @@ -268,7 +265,7 @@ enum ContentEncoder { Gzip(GzEncoder), #[cfg(feature = "compress-brotli")] - Brotli(BrotliEncoder), + Brotli(Box>), // Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we // use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`. @@ -292,9 +289,7 @@ impl ContentEncoder { ))), #[cfg(feature = "compress-brotli")] - ContentEncoding::Brotli => { - Some(ContentEncoder::Brotli(BrotliEncoder::new(Writer::new(), 3))) - } + ContentEncoding::Brotli => Some(ContentEncoder::Brotli(new_brotli_compressor())), #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => { @@ -326,8 +321,8 @@ impl ContentEncoder { fn finish(self) -> Result { match self { #[cfg(feature = "compress-brotli")] - ContentEncoder::Brotli(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), + ContentEncoder::Brotli(mut encoder) => match encoder.flush() { + Ok(()) => Ok(encoder.into_inner().buf.freeze()), Err(err) => Err(err), }, @@ -392,6 +387,16 @@ impl ContentEncoder { } } +#[cfg(feature = "compress-brotli")] +fn new_brotli_compressor() -> Box> { + Box::new(brotli::CompressorWriter::new( + Writer::new(), + 8 * 1024, // 32 KiB buffer + 3, // BROTLI_PARAM_QUALITY + 22, // BROTLI_PARAM_LGWIN + )) +} + #[derive(Debug, Display)] #[non_exhaustive] pub enum EncoderError { diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8accf4d0d..16c2083d8 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -101,7 +101,7 @@ actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.20", features = ["openssl"] } -brotli2 = "0.3.2" +brotli = "3.3.3" const-str = "0.3" env_logger = "0.9" flate2 = "1.0.13" diff --git a/awc/tests/utils.rs b/awc/tests/utils.rs index 9a3743d8b..2532640c6 100644 --- a/awc/tests/utils.rs +++ b/awc/tests/utils.rs @@ -41,16 +41,22 @@ pub mod deflate { pub mod brotli { use super::*; - use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder}; + use ::brotli::{reader::Decompressor as BrotliDecoder, CompressorWriter as BrotliEncoder}; pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { - let mut encoder = BrotliEncoder::new(Vec::new(), 3); + let mut encoder = BrotliEncoder::new( + Vec::new(), + 8 * 1024, // 32 KiB buffer + 3, // BROTLI_PARAM_QUALITY + 22, // BROTLI_PARAM_LGWIN + ); encoder.write_all(bytes.as_ref()).unwrap(); - encoder.finish().unwrap() + encoder.flush().unwrap(); + encoder.into_inner() } pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { - let mut decoder = BrotliDecoder::new(bytes.as_ref()); + let mut decoder = BrotliDecoder::new(bytes.as_ref(), 8_096); let mut buf = Vec::new(); decoder.read_to_end(&mut buf).unwrap(); buf diff --git a/tests/utils.rs b/tests/utils.rs index 9a3743d8b..2532640c6 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -41,16 +41,22 @@ pub mod deflate { pub mod brotli { use super::*; - use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder}; + use ::brotli::{reader::Decompressor as BrotliDecoder, CompressorWriter as BrotliEncoder}; pub fn encode(bytes: impl AsRef<[u8]>) -> Vec { - let mut encoder = BrotliEncoder::new(Vec::new(), 3); + let mut encoder = BrotliEncoder::new( + Vec::new(), + 8 * 1024, // 32 KiB buffer + 3, // BROTLI_PARAM_QUALITY + 22, // BROTLI_PARAM_LGWIN + ); encoder.write_all(bytes.as_ref()).unwrap(); - encoder.finish().unwrap() + encoder.flush().unwrap(); + encoder.into_inner() } pub fn decode(bytes: impl AsRef<[u8]>) -> Vec { - let mut decoder = BrotliDecoder::new(bytes.as_ref()); + let mut decoder = BrotliDecoder::new(bytes.as_ref(), 8_096); let mut buf = Vec::new(); decoder.read_to_end(&mut buf).unwrap(); buf From 3c7ccf55210ea97951dbb6a2f53bb3458ac5f65b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 15 Jan 2022 15:43:18 +0000 Subject: [PATCH 279/861] update http changelog --- actix-http/CHANGES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 935e35561..a58846405 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Brotli (de)compression support is now provided by the `brotli` crate. [#2538] + +[#2538]: https://github.com/actix/actix-web/pull/2538 ## 3.0.0-beta.18 - 2022-01-04 @@ -15,8 +19,8 @@ - `Quality::MIN` is now the smallest non-zero value. [#2501] - `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501] - Rename `ContentEncoding::{Br => Brotli}`. [#2501] -- Minimum supported Rust version (MSRV) is now 1.54. - Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565] +- Minimum supported Rust version (MSRV) is now 1.54. ### Fixed - `ContentEncoding::Identity` can now be parsed from a string. [#2501] From 7b8a392ef5c5c535457d852206a87d31d1ac493b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 16 Jan 2022 03:16:26 +0000 Subject: [PATCH 280/861] allow camel case response headers (#2587) --- actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 1 + actix-http/src/h1/encoder.rs | 6 +++ actix-http/src/responses/head.rs | 68 +++++++++++++++++++++++++++++++- 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a58846405..d03a45969 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- Response headers can be sent as camel case using `res.head_mut().set_camel_case_headers(true)`. [#2587] + ### Changed - Brotli (de)compression support is now provided by the `brotli` crate. [#2538] [#2538]: https://github.com/actix/actix-web/pull/2538 +[#2587]: https://github.com/actix/actix-web/pull/2587 ## 3.0.0-beta.18 - 2022-01-04 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index df9e11419..163fce931 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -88,6 +88,7 @@ async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +memchr = "2.4" rcgen = "0.8" regex = "1.3" rustls-pemfile = "0.2" diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index f2a862278..8b1e3b623 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -258,6 +258,12 @@ impl MessageType for Response<()> { None } + fn camel_case(&self) -> bool { + self.head() + .flags + .contains(crate::message::Flags::CAMEL_CASE) + } + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { let head = self.head(); let reason = head.reason().as_bytes(); diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index 78d9536e5..d11ba8fde 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -20,7 +20,7 @@ pub struct ResponseHead { pub headers: HeaderMap, pub reason: Option<&'static str>, pub(crate) extensions: RefCell, - flags: Flags, + pub(crate) flags: Flags, } impl ResponseHead { @@ -49,6 +49,18 @@ impl ResponseHead { &mut self.headers } + /// Sets the flag that controls wether to send headers formatted as Camel-Case. + /// + /// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase. + #[inline] + pub fn set_camel_case_headers(&mut self, camel_case: bool) { + if camel_case { + self.flags.insert(Flags::CAMEL_CASE); + } else { + self.flags.remove(Flags::CAMEL_CASE); + } + } + /// Message extensions #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { @@ -206,3 +218,57 @@ impl BoxedResponsePool { } } } + +#[cfg(test)] +mod tests { + use std::{ + io::{Read as _, Write as _}, + net, + }; + + use memchr::memmem; + + use crate::{ + header::{HeaderName, HeaderValue}, + Error, HttpService, Request, Response, + }; + + #[actix_rt::test] + async fn camel_case_headers() { + let mut srv = actix_http_test::test_server(|| { + HttpService::new(|req: Request| async move { + let mut res = Response::ok(); + + if req.path().contains("camel") { + res.head_mut().set_camel_case_headers(true); + } + + res.headers_mut().insert( + HeaderName::from_static("foo-bar"), + HeaderValue::from_static("baz"), + ); + Ok::<_, Error>(res) + }) + .tcp() + }) + .await; + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert!(memmem::find(&data, b"Foo-Bar").is_some()); + assert!(!memmem::find(&data, b"foo-bar").is_some()); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert!(!memmem::find(&data, b"Foo-Bar").is_some()); + assert!(memmem::find(&data, b"foo-bar").is_some()); + + srv.stop().await; + } +} From 2ffc21dd4f8925d22206d23b7c21d62b83a68ee4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 02:09:25 +0000 Subject: [PATCH 281/861] move response extensions out of head (#2585) --- CHANGES.md | 4 ++ actix-http/CHANGES.md | 6 +++ actix-http/examples/echo.rs | 12 +++--- actix-http/examples/echo2.rs | 28 +++++++------- actix-http/src/error.rs | 23 +++--------- actix-http/src/http_message.rs | 4 +- actix-http/src/message.rs | 8 ++-- actix-http/src/requests/head.rs | 2 +- actix-http/src/requests/request.rs | 23 ++++++------ actix-http/src/responses/builder.rs | 26 ++++--------- actix-http/src/responses/head.rs | 45 +++++++--------------- actix-http/src/responses/response.rs | 33 +++++++++++----- awc/src/responses/response.rs | 15 ++++++-- scripts/bump | 2 +- src/app_service.rs | 32 ++++++++-------- src/guard.rs | 10 ++--- src/request.rs | 38 +++++++++---------- src/request_data.rs | 13 ++++--- src/response/builder.rs | 28 ++++++-------- src/response/response.rs | 23 +++++++----- src/service.rs | 56 +++++++++++----------------- 21 files changed, 205 insertions(+), 226 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa4feab6e..31b17cba8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +- `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] + +[#2585]: https://github.com/actix/actix-web/pull/2585 ## 4.0.0-beta.20 - 2022-01-14 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d03a45969..7fd635e3d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,11 +3,17 @@ ## Unreleased - 2021-xx-xx ### Added - Response headers can be sent as camel case using `res.head_mut().set_camel_case_headers(true)`. [#2587] +- `ResponseHead` now implements `Clone`. [#2585] ### Changed - Brotli (de)compression support is now provided by the `brotli` crate. [#2538] +### Removed +- `ResponseHead::extensions[_mut]()`. [#2585] +- `ResponseBuilder::extensions[_mut]()`. [#2585] + [#2538]: https://github.com/actix/actix-web/pull/2538 +[#2585]: https://github.com/actix/actix-web/pull/2585 [#2587]: https://github.com/actix/actix-web/pull/2587 diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 22f553f38..f9188ed9f 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -15,6 +15,7 @@ async fn main() -> io::Result<()> { HttpService::build() .client_timeout(1000) .client_disconnect(1000) + // handles HTTP/1.1 and HTTP/2 .finish(|mut req: Request| async move { let mut body = BytesMut::new(); while let Some(item) = req.payload().next().await { @@ -23,12 +24,13 @@ async fn main() -> io::Result<()> { log::info!("request body: {:?}", body); - Ok::<_, Error>( - Response::build(StatusCode::OK) - .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) - .body(body), - ) + let res = Response::build(StatusCode::OK) + .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) + .body(body); + + Ok::<_, Error>(res) }) + // No TLS .tcp() })? .run() diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index e3b915e05..605572d8b 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,32 +1,34 @@ use std::io; use actix_http::{ - body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode, + body::{BodyStream, MessageBody}, + header, Error, HttpMessage, HttpService, Request, Response, StatusCode, }; -use actix_server::Server; -use bytes::BytesMut; -use futures_util::StreamExt as _; async fn handle_request(mut req: Request) -> Result, Error> { - let mut body = BytesMut::new(); - while let Some(item) = req.payload().next().await { - body.extend_from_slice(&item?) + let mut res = Response::build(StatusCode::OK); + + if let Some(ct) = req.headers().get(header::CONTENT_TYPE) { + res.insert_header((header::CONTENT_TYPE, ct)); } - log::info!("request body: {:?}", body); + // echo request payload stream as (chunked) response body + let res = res.message_body(BodyStream::new(req.payload().take()))?; - Ok(Response::build(StatusCode::OK) - .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) - .body(body)) + Ok(res) } #[actix_rt::main] async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - Server::build() + actix_server::Server::build() .bind("echo", ("127.0.0.1", 8080), || { - HttpService::build().finish(handle_request).tcp() + HttpService::build() + // handles HTTP/1.1 only + .h1(handle_request) + // No TLS + .tcp() })? .run() .await diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cdf495c45..df6d3813a 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -387,6 +387,7 @@ impl StdError for DispatchError { /// A set of error that can occur during parsing content type. #[derive(Debug, Display, Error)] +#[cfg_attr(test, derive(PartialEq))] #[non_exhaustive] pub enum ContentTypeError { /// Can not parse content type @@ -398,28 +399,14 @@ pub enum ContentTypeError { UnknownEncoding, } -#[cfg(test)] -mod content_type_test_impls { - use super::*; - - impl std::cmp::PartialEq for ContentTypeError { - fn eq(&self, other: &Self) -> bool { - match self { - Self::ParseError => matches!(other, ContentTypeError::ParseError), - Self::UnknownEncoding => { - matches!(other, ContentTypeError::UnknownEncoding) - } - } - } - } -} - #[cfg(test)] mod tests { - use super::*; - use http::{Error as HttpError, StatusCode}; use std::io; + use http::{Error as HttpError, StatusCode}; + + use super::*; + #[test] fn test_into_response() { let resp: Response = ParseError::Incomplete.into(); diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index ccaa320fa..068e23b96 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -25,10 +25,10 @@ pub trait HttpMessage: Sized { /// Message payload stream fn take_payload(&mut self) -> Payload; - /// Request's extensions container + /// Returns a reference to the request-local data/extensions container. fn extensions(&self) -> Ref<'_, Extensions>; - /// Mutable reference to a the request's extensions container + /// Returns a mutable reference to the request-local data/extensions container. fn extensions_mut(&self) -> RefMut<'_, Extensions>; /// Get a header. diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index ecd08fbb3..5616a4762 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -5,13 +5,13 @@ use bitflags::bitflags; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] pub enum ConnectionType { - /// Close connection after response + /// Close connection after response. Close, - /// Keep connection alive after response + /// Keep connection alive after response. KeepAlive, - /// Connection is upgraded to different type + /// Connection is upgraded to different type. Upgrade, } @@ -69,8 +69,8 @@ impl Drop for Message { } } +/// Generic `Head` object pool. #[doc(hidden)] -/// Request's objects pool pub struct MessagePool(RefCell>>); impl MessagePool { diff --git a/actix-http/src/requests/head.rs b/actix-http/src/requests/head.rs index 524075b61..06fd0429e 100644 --- a/actix-http/src/requests/head.rs +++ b/actix-http/src/requests/head.rs @@ -142,8 +142,8 @@ impl RequestHead { } } -#[derive(Debug)] #[allow(clippy::large_enum_variant)] +#[derive(Debug)] pub enum RequestHeadType { Owned(RequestHead), Rc(Rc, Option), diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 4eaaba8e1..0f8e78d46 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -19,7 +19,7 @@ pub struct Request

{ pub(crate) payload: Payload

, pub(crate) head: Message, pub(crate) conn_data: Option>, - pub(crate) req_data: RefCell, + pub(crate) extensions: RefCell, } impl

HttpMessage for Request

{ @@ -34,16 +34,14 @@ impl

HttpMessage for Request

{ mem::replace(&mut self.payload, Payload::None) } - /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.req_data.borrow() + self.extensions.borrow() } - /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.req_data.borrow_mut() + self.extensions.borrow_mut() } } @@ -52,7 +50,7 @@ impl From> for Request { Request { head, payload: Payload::None, - req_data: RefCell::new(Extensions::default()), + extensions: RefCell::new(Extensions::default()), conn_data: None, } } @@ -65,7 +63,7 @@ impl Request { Request { head: Message::new(), payload: Payload::None, - req_data: RefCell::new(Extensions::default()), + extensions: RefCell::new(Extensions::default()), conn_data: None, } } @@ -77,7 +75,7 @@ impl

Request

{ Request { payload, head: Message::new(), - req_data: RefCell::new(Extensions::default()), + extensions: RefCell::new(Extensions::default()), conn_data: None, } } @@ -90,7 +88,7 @@ impl

Request

{ Request { payload, head: self.head, - req_data: self.req_data, + extensions: self.extensions, conn_data: self.conn_data, }, pl, @@ -195,16 +193,17 @@ impl

Request

{ .and_then(|container| container.get::()) } - /// Returns the connection data container if an [on-connect] callback was registered. + /// Returns the connection-level data/extensions container if an [on-connect] callback was + /// registered, leaving an empty one in its place. /// /// [on-connect]: crate::HttpServiceBuilder::on_connect_ext pub fn take_conn_data(&mut self) -> Option> { self.conn_data.take() } - /// Returns the request data container, leaving an empty one in it's place. + /// Returns the request-local data/extensions container, leaving an empty one in its place. pub fn take_req_data(&mut self) -> Extensions { - mem::take(self.req_data.get_mut()) + mem::take(self.extensions.get_mut()) } } diff --git a/actix-http/src/responses/builder.rs b/actix-http/src/responses/builder.rs index 5854863de..4a67423b1 100644 --- a/actix-http/src/responses/builder.rs +++ b/actix-http/src/responses/builder.rs @@ -1,9 +1,6 @@ //! HTTP response builder. -use std::{ - cell::{Ref, RefMut}, - fmt, str, -}; +use std::{cell::RefCell, fmt, str}; use crate::{ body::{EitherBody, MessageBody}, @@ -202,20 +199,6 @@ impl ResponseBuilder { self } - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow_mut() - } - /// Generate response with a wrapped body. /// /// This `ResponseBuilder` will be left in a useless state. @@ -238,7 +221,12 @@ impl ResponseBuilder { } let head = self.head.take().expect("cannot reuse response builder"); - Ok(Response { head, body }) + + Ok(Response { + head, + body, + extensions: RefCell::new(Extensions::new()), + }) } /// Generate response with an empty body. diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index d11ba8fde..91e96a928 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -1,25 +1,19 @@ //! Response head type and caching pool. -use std::{ - cell::{Ref, RefCell, RefMut}, - ops, -}; +use std::{cell::RefCell, ops}; -use crate::{ - header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version, -}; +use crate::{header::HeaderMap, message::Flags, ConnectionType, StatusCode, Version}; thread_local! { static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create(); } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ResponseHead { pub version: Version, pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, - pub(crate) extensions: RefCell, pub(crate) flags: Flags, } @@ -33,18 +27,17 @@ impl ResponseHead { headers: HeaderMap::with_capacity(12), reason: None, flags: Flags::empty(), - extensions: RefCell::new(Extensions::new()), } } - #[inline] /// Read the message headers. + #[inline] pub fn headers(&self) -> &HeaderMap { &self.headers } - #[inline] /// Mutable reference to the message headers. + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } @@ -61,20 +54,8 @@ impl ResponseHead { } } - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.extensions.borrow_mut() - } - - #[inline] /// Set connection type of the message + #[inline] pub fn set_connection_type(&mut self, ctype: ConnectionType) { match ctype { ConnectionType::Close => self.flags.insert(Flags::CLOSE), @@ -133,14 +114,14 @@ impl ResponseHead { } } - #[inline] /// Get response body chunking state + #[inline] pub fn chunked(&self) -> bool { !self.flags.contains(Flags::NO_CHUNKING) } - #[inline] /// Set no chunking for payload + #[inline] pub fn no_chunking(&mut self, val: bool) { if val { self.flags.insert(Flags::NO_CHUNKING); @@ -183,7 +164,7 @@ impl Drop for BoxedResponseHead { } } -/// Request's objects pool +/// Response head object pool. #[doc(hidden)] pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell>>); @@ -192,7 +173,7 @@ impl BoxedResponsePool { BoxedResponsePool(RefCell::new(Vec::with_capacity(128))) } - /// Get message from the pool + /// Get message from the pool. #[inline] fn get_message(&self, status: StatusCode) -> BoxedResponseHead { if let Some(mut head) = self.0.borrow_mut().pop() { @@ -208,12 +189,12 @@ impl BoxedResponsePool { } } - /// Release request instance + /// Release request instance. #[inline] - fn release(&self, mut msg: Box) { + fn release(&self, msg: Box) { let pool = &mut self.0.borrow_mut(); + if pool.len() < 128 { - msg.extensions.get_mut().clear(); pool.push(msg); } } diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index ec9157afb..6efc3c5f1 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -1,7 +1,7 @@ //! HTTP response. use std::{ - cell::{Ref, RefMut}, + cell::{Ref, RefCell, RefMut}, fmt, str, }; @@ -9,7 +9,7 @@ use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use crate::{ - body::{BoxBody, MessageBody}, + body::{BoxBody, EitherBody, MessageBody}, header::{self, HeaderMap, TryIntoHeaderValue}, responses::BoxedResponseHead, Error, Extensions, ResponseBuilder, ResponseHead, StatusCode, @@ -19,6 +19,7 @@ use crate::{ pub struct Response { pub(crate) head: BoxedResponseHead, pub(crate) body: B, + pub(crate) extensions: RefCell, } impl Response { @@ -28,6 +29,7 @@ impl Response { Response { head: BoxedResponseHead::new(status), body: BoxBody::new(()), + extensions: RefCell::new(Extensions::new()), } } @@ -74,6 +76,7 @@ impl Response { Response { head: BoxedResponseHead::new(status), body, + extensions: RefCell::new(Extensions::new()), } } @@ -120,20 +123,21 @@ impl Response { } /// Returns true if keep-alive is enabled. + #[inline] pub fn keep_alive(&self) -> bool { self.head.keep_alive() } - /// Returns a reference to the extensions of this response. + /// Returns a reference to the request-local data/extensions container. #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions.borrow() + self.extensions.borrow() } - /// Returns a mutable reference to the extensions of this response. + /// Returns a mutable reference to the request-local data/extensions container. #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - self.head.extensions.borrow_mut() + self.extensions.borrow_mut() } /// Returns a reference to the body of this response. @@ -143,24 +147,29 @@ impl Response { } /// Sets new body. + #[inline] pub fn set_body(self, body: B2) -> Response { Response { head: self.head, body, + extensions: self.extensions, } } /// Drops body and returns new response. + #[inline] pub fn drop_body(self) -> Response<()> { self.set_body(()) } /// Sets new body, returning new response and previous body value. + #[inline] pub(crate) fn replace_body(self, body: B2) -> (Response, B) { ( Response { head: self.head, body, + extensions: self.extensions, }, self.body, ) @@ -171,11 +180,15 @@ impl Response { /// # Implementation Notes /// Due to internal performance optimizations, the first element of the returned tuple is a /// `Response` as well but only contains the head of the response this was called on. + #[inline] pub fn into_parts(self) -> (Response<()>, B) { self.replace_body(()) } - /// Returns new response with mapped body. + /// Map the current body type to another using a closure. Returns a new response. + /// + /// Closure receives the response head and the current body type. + #[inline] pub fn map_body(mut self, f: F) -> Response where F: FnOnce(&mut ResponseHead, B) -> B2, @@ -185,6 +198,7 @@ impl Response { Response { head: self.head, body, + extensions: self.extensions, } } @@ -197,6 +211,7 @@ impl Response { } /// Returns body, consuming this response. + #[inline] pub fn into_body(self) -> B { self.body } @@ -239,9 +254,9 @@ impl>, E: Into> From> for Response } } -impl From for Response { +impl From for Response> { fn from(mut builder: ResponseBuilder) -> Self { - builder.finish().map_into_boxed_body() + builder.finish() } } diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs index 0197265f1..02ffdbab2 100644 --- a/awc/src/responses/response.rs +++ b/awc/src/responses/response.rs @@ -1,5 +1,5 @@ use std::{ - cell::{Ref, RefMut}, + cell::{Ref, RefCell, RefMut}, fmt, mem, pin::Pin, task::{Context, Poll}, @@ -28,6 +28,8 @@ pin_project! { #[pin] pub(crate) payload: Payload, pub(crate) timeout: ResponseTimeout, + pub(crate) extensions: RefCell, + } } @@ -38,6 +40,7 @@ impl ClientResponse { head, payload, timeout: ResponseTimeout::default(), + extensions: RefCell::new(Extensions::new()), } } @@ -64,7 +67,9 @@ impl ClientResponse { &self.head().headers } - /// Set a body and return previous body value + /// Map the current body type to another using a closure. Returns a new response. + /// + /// Closure receives the response head and the current body type. pub fn map_body(mut self, f: F) -> ClientResponse where F: FnOnce(&mut ResponseHead, Payload) -> Payload, @@ -75,6 +80,7 @@ impl ClientResponse { payload, head: self.head, timeout: self.timeout, + extensions: self.extensions, } } @@ -101,6 +107,7 @@ impl ClientResponse { payload: self.payload, head: self.head, timeout, + extensions: self.extensions, } } @@ -224,11 +231,11 @@ impl HttpMessage for ClientResponse { } fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions() + self.extensions.borrow() } fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head.extensions_mut() + self.extensions.borrow_mut() } } diff --git a/scripts/bump b/scripts/bump index c43b92dc8..209e2281d 100755 --- a/scripts/bump +++ b/scripts/bump @@ -31,7 +31,7 @@ fi # get current version PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" -CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")" +CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" CHANGE_CHUNK_FILE="$(mktemp)" echo saving changelog to $CHANGE_CHUNK_FILE diff --git a/src/app_service.rs b/src/app_service.rs index 56b24f0d8..b7c016e81 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -201,27 +201,29 @@ where actix_service::forward_ready!(service); fn call(&self, mut req: Request) -> Self::Future { - let req_data = Rc::new(RefCell::new(req.take_req_data())); + let extensions = Rc::new(RefCell::new(req.take_req_data())); let conn_data = req.take_conn_data(); let (head, payload) = req.into_parts(); - let req = if let Some(mut req) = self.app_state.pool().pop() { - let inner = Rc::get_mut(&mut req.inner).unwrap(); - inner.path.get_mut().update(&head.uri); - inner.path.reset(); - inner.head = head; - inner.conn_data = conn_data; - inner.req_data = req_data; - req - } else { - HttpRequest::new( + let req = match self.app_state.pool().pop() { + Some(mut req) => { + let inner = Rc::get_mut(&mut req.inner).unwrap(); + inner.path.get_mut().update(&head.uri); + inner.path.reset(); + inner.head = head; + inner.conn_data = conn_data; + inner.extensions = extensions; + req + } + + None => HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, - self.app_state.clone(), - self.app_data.clone(), + Rc::clone(&self.app_state), + Rc::clone(&self.app_data), conn_data, - req_data, - ) + extensions, + ), }; self.service.call(ServiceRequest::new(req, payload)) diff --git a/src/guard.rs b/src/guard.rs index f4200a382..596b9f9fe 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -54,7 +54,7 @@ use std::{ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead}; -use crate::{http::header::Header, service::ServiceRequest}; +use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _}; /// Provides access to request parts that are useful during routing. #[derive(Debug)] @@ -69,16 +69,16 @@ impl<'a> GuardContext<'a> { self.req.head() } - /// Returns reference to the request-local data container. + /// Returns reference to the request-local data/extensions container. #[inline] pub fn req_data(&self) -> Ref<'a, Extensions> { - self.req.req_data() + self.req.extensions() } - /// Returns mutable reference to the request-local data container. + /// Returns mutable reference to the request-local data/extensions container. #[inline] pub fn req_data_mut(&self) -> RefMut<'a, Extensions> { - self.req.req_data_mut() + self.req.extensions_mut() } /// Extracts a typed header from the request. diff --git a/src/request.rs b/src/request.rs index e876c3b4d..bcab79205 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,10 +5,7 @@ use std::{ str, }; -use actix_http::{ - header::HeaderMap, Extensions, HttpMessage, Message, Method, Payload, RequestHead, Uri, - Version, -}; +use actix_http::{Message, RequestHead}; use actix_router::{Path, Url}; use actix_utils::future::{ok, Ready}; #[cfg(feature = "cookies")] @@ -16,8 +13,14 @@ use cookie::{Cookie, ParseError as CookieParseError}; use smallvec::SmallVec; use crate::{ - app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, - info::ConnectionInfo, rmap::ResourceMap, Error, FromRequest, + app_service::AppInitServiceState, + config::AppConfig, + dev::{Extensions, Payload}, + error::UrlGenerationError, + http::{header::HeaderMap, Method, Uri, Version}, + info::ConnectionInfo, + rmap::ResourceMap, + Error, FromRequest, HttpMessage, }; #[cfg(feature = "cookies")] @@ -38,7 +41,7 @@ pub(crate) struct HttpRequestInner { pub(crate) path: Path, pub(crate) app_data: SmallVec<[Rc; 4]>, pub(crate) conn_data: Option>, - pub(crate) req_data: Rc>, + pub(crate) extensions: Rc>, app_state: Rc, } @@ -50,7 +53,7 @@ impl HttpRequest { app_state: Rc, app_data: Rc, conn_data: Option>, - req_data: Rc>, + extensions: Rc>, ) -> HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); @@ -62,7 +65,7 @@ impl HttpRequest { app_state, app_data: data, conn_data, - req_data, + extensions, }), } } @@ -159,14 +162,6 @@ impl HttpRequest { self.resource_map().match_name(self.path()) } - pub fn req_data(&self) -> Ref<'_, Extensions> { - self.inner.req_data.borrow() - } - - pub fn req_data_mut(&self) -> RefMut<'_, Extensions> { - self.inner.req_data.borrow_mut() - } - /// Returns a reference a piece of connection data set in an [on-connect] callback. /// /// ```ignore @@ -356,12 +351,12 @@ impl HttpMessage for HttpRequest { #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.req_data() + self.inner.extensions.borrow() } #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.req_data_mut() + self.inner.extensions.borrow_mut() } #[inline] @@ -382,7 +377,10 @@ impl Drop for HttpRequest { // Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also // we know the req_data Rc will not have any cloned at this point to unwrap is okay. - Rc::get_mut(&mut inner.req_data).unwrap().get_mut().clear(); + Rc::get_mut(&mut inner.extensions) + .unwrap() + .get_mut() + .clear(); // a re-borrow of pool is necessary here. let req = Rc::clone(&self.inner); diff --git a/src/request_data.rs b/src/request_data.rs index b685fd0d6..68103a7e9 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -2,7 +2,10 @@ use std::{any::type_name, ops::Deref}; use actix_utils::future::{err, ok, Ready}; -use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpRequest}; +use crate::{ + dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpMessage as _, + HttpRequest, +}; /// Request-local data extractor. /// @@ -17,13 +20,13 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H /// # Mutating Request Data /// Note that since extractors must output owned data, only types that `impl Clone` can use this /// extractor. A clone is taken of the required request data and can, therefore, not be directly -/// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or +/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or /// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not /// provided to make this potential foot-gun more obvious. /// /// # Example /// ```no_run -/// # use actix_web::{web, HttpResponse, HttpRequest, Responder}; +/// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _}; /// /// #[derive(Debug, Clone, PartialEq)] /// struct FlagFromMiddleware(String); @@ -35,7 +38,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H /// ) -> impl Responder { /// // use an option extractor if middleware is not guaranteed to add this type of req data /// if let Some(flag) = opt_flag { -/// assert_eq!(&flag.into_inner(), req.req_data().get::().unwrap()); +/// assert_eq!(&flag.into_inner(), req.extensions().get::().unwrap()); /// } /// /// HttpResponse::Ok() @@ -67,7 +70,7 @@ impl FromRequest for ReqData { type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if let Some(st) = req.req_data().get::() { + if let Some(st) = req.extensions().get::() { ok(ReqData(st.clone())) } else { log::debug!( diff --git a/src/response/builder.rs b/src/response/builder.rs index bdb0aaa12..c8e44729a 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -7,7 +7,6 @@ use std::{ }; use actix_http::{ - body::{BodyStream, BoxBody, MessageBody}, error::HttpError, header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue}, ConnectionType, Extensions, Response, ResponseHead, StatusCode, @@ -16,12 +15,8 @@ use bytes::Bytes; use futures_core::Stream; use serde::Serialize; -#[cfg(feature = "cookies")] -use actix_http::header::HeaderValue; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; - use crate::{ + body::{BodyStream, BoxBody, MessageBody}, error::{Error, JsonPayloadError}, BoxError, HttpRequest, HttpResponse, Responder, }; @@ -33,7 +28,7 @@ pub struct HttpResponseBuilder { res: Option>, err: Option, #[cfg(feature = "cookies")] - cookies: Option, + cookies: Option, } impl HttpResponseBuilder { @@ -242,9 +237,9 @@ impl HttpResponseBuilder { /// .finish(); /// ``` #[cfg(feature = "cookies")] - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + pub fn cookie<'c>(&mut self, cookie: cookie::Cookie<'c>) -> &mut Self { if self.cookies.is_none() { - let mut jar = CookieJar::new(); + let mut jar = cookie::CookieJar::new(); jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { @@ -271,9 +266,9 @@ impl HttpResponseBuilder { /// } /// ``` #[cfg(feature = "cookies")] - pub fn del_cookie(&mut self, cookie: &Cookie<'_>) -> &mut Self { + pub fn del_cookie(&mut self, cookie: &cookie::Cookie<'_>) -> &mut Self { if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) + self.cookies = Some(cookie::CookieJar::new()) } let jar = self.cookies.as_mut().unwrap(); let cookie = cookie.clone().into_owned(); @@ -282,7 +277,7 @@ impl HttpResponseBuilder { self } - /// Responses extensions + /// Returns a reference to the response-local data/extensions container. #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { self.res @@ -291,7 +286,8 @@ impl HttpResponseBuilder { .extensions() } - /// Mutable reference to a the response's extensions + /// Returns a mutable reference to the response-local data/extensions container. + #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { self.res .as_mut() @@ -332,7 +328,7 @@ impl HttpResponseBuilder { #[cfg(feature = "cookies")] if let Some(ref jar) = self.cookies { for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { + match actix_http::header::HeaderValue::from_str(&cookie.to_string()) { Ok(val) => res.headers_mut().append(header::SET_COOKIE, val), Err(err) => return Err(err.into()), }; @@ -394,7 +390,6 @@ impl HttpResponseBuilder { } } - #[inline] fn inner(&mut self) -> Option<&mut ResponseHead> { if self.err.is_some() { return None; @@ -435,10 +430,9 @@ impl Responder for HttpResponseBuilder { #[cfg(test)] mod tests { - use actix_http::body; - use super::*; use crate::{ + body, http::{ header::{self, HeaderValue, CONTENT_TYPE}, StatusCode, diff --git a/src/response/response.rs b/src/response/response.rs index f24a75b19..4aba4b623 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -168,34 +168,37 @@ impl HttpResponse { self.res.keep_alive() } - /// Responses extensions + /// Returns reference to the response-local data/extensions container. #[inline] pub fn extensions(&self) -> Ref<'_, Extensions> { self.res.extensions() } - /// Mutable reference to a the response's extensions + /// Returns reference to the response-local data/extensions container. #[inline] pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { self.res.extensions_mut() } - /// Get body of this response + /// Returns a reference to this response's body. #[inline] pub fn body(&self) -> &B { self.res.body() } - /// Set a body + /// Sets new body. pub fn set_body(self, body: B2) -> HttpResponse { HttpResponse { res: self.res.set_body(body), - error: None, - // error: self.error, ?? + error: self.error, } } - /// Split response and body + /// Returns split head and body. + /// + /// # Implementation Notes + /// Due to internal performance optimizations, the first element of the returned tuple is an + /// `HttpResponse` as well but only contains the head of the response this was called on. pub fn into_parts(self) -> (HttpResponse<()>, B) { let (head, body) = self.res.into_parts(); @@ -208,7 +211,7 @@ impl HttpResponse { ) } - /// Drop request's body + /// Drops body and returns new response. pub fn drop_body(self) -> HttpResponse<()> { HttpResponse { res: self.res.drop_body(), @@ -216,7 +219,9 @@ impl HttpResponse { } } - /// Set a body and return previous body value + /// Map the current body type to another using a closure. Returns a new response. + /// + /// Closure receives the response head and the current body type. pub fn map_body(self, f: F) -> HttpResponse where F: FnOnce(&mut ResponseHead, B) -> B2, diff --git a/src/service.rs b/src/service.rs index f15cbfc9f..03ea0b97b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -103,6 +103,7 @@ impl ServiceRequest { /// Construct request from request. /// /// The returned `ServiceRequest` would have no payload. + #[inline] pub fn from_request(req: HttpRequest) -> Self { ServiceRequest { req, @@ -256,18 +257,6 @@ impl ServiceRequest { self.req.conn_data() } - /// Counterpart to [`HttpRequest::req_data`]. - #[inline] - pub fn req_data(&self) -> Ref<'_, Extensions> { - self.req.req_data() - } - - /// Counterpart to [`HttpRequest::req_data_mut`]. - #[inline] - pub fn req_data_mut(&self) -> RefMut<'_, Extensions> { - self.req.req_data_mut() - } - #[cfg(feature = "cookies")] #[inline] pub fn cookies(&self) -> Result>>, CookieParseError> { @@ -320,18 +309,15 @@ impl HttpMessage for ServiceRequest { type Stream = BoxedPayloadStream; #[inline] - /// Returns Request's headers. fn headers(&self) -> &HeaderMap { &self.head().headers } - /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { self.req.extensions() } - /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { self.req.extensions_mut() @@ -398,32 +384,32 @@ impl ServiceResponse { ServiceResponse::new(self.request, response) } - /// Get reference to original request + /// Returns reference to original request. #[inline] pub fn request(&self) -> &HttpRequest { &self.request } - /// Get reference to response + /// Returns reference to response. #[inline] pub fn response(&self) -> &HttpResponse { &self.response } - /// Get mutable reference to response + /// Returns mutable reference to response. #[inline] pub fn response_mut(&mut self) -> &mut HttpResponse { &mut self.response } - /// Get the response status code + /// Returns response status code. #[inline] pub fn status(&self) -> StatusCode { self.response.status() } - #[inline] /// Returns response's headers. + #[inline] pub fn headers(&self) -> &HeaderMap { self.response.headers() } @@ -440,13 +426,9 @@ impl ServiceResponse { (self.request, self.response) } - /// Extract response body - #[inline] - pub fn into_body(self) -> B { - self.response.into_body() - } - - /// Set a new body + /// Map the current body type to another using a closure. Returns a new response. + /// + /// Closure receives the response head and the current body type. #[inline] pub fn map_body(self, f: F) -> ServiceResponse where @@ -477,6 +459,12 @@ impl ServiceResponse { { self.map_body(|_, body| body.boxed()) } + + /// Consumes the response and returns its body. + #[inline] + pub fn into_body(self) -> B { + self.response.into_body() + } } impl From> for HttpResponse { @@ -546,14 +534,12 @@ impl WebService { /// Ok(req.into_response(HttpResponse::Ok().finish())) /// } /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::service("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .finish(index) - /// ); - /// } + /// let app = App::new() + /// .service( + /// web::service("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .finish(index) + /// ); /// ``` pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(guard)); From ad159f52196f96b620788529c8621210447814bb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 15:52:16 +0000 Subject: [PATCH 282/861] fix ClientResponse::body doc fixes #2589 --- awc/src/responses/response.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs index 02ffdbab2..4e6a05f0f 100644 --- a/awc/src/responses/response.rs +++ b/awc/src/responses/response.rs @@ -160,7 +160,6 @@ where /// /// # Errors /// `Future` implementation returns error if: - /// - content type is not `application/json` /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB) /// /// # Examples From 5ee555462f54768a797e7af1548bc89be36d292f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 16:36:11 +0000 Subject: [PATCH 283/861] add `HttpResponse::add_removal_cookie` (#2586) --- CHANGES.md | 4 +++ src/response/response.rs | 73 +++++++++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 31b17cba8..fae671072 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- `HttpResponse::add_removal_cookie` [#2586] + ### Removed - `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] [#2585]: https://github.com/actix/actix-web/pull/2585 +[#2586]: https://github.com/actix/actix-web/pull/2586 ## 4.0.0-beta.20 - 2022-01-14 diff --git a/src/response/response.rs b/src/response/response.rs index 4aba4b623..33f0a54a6 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -27,7 +27,7 @@ use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder}; /// An outgoing response. pub struct HttpResponse { res: Response, - pub(crate) error: Option, + error: Option, } impl HttpResponse { @@ -116,18 +116,54 @@ impl HttpResponse { } } - /// Add a cookie to this response + /// Add a cookie to this response. + /// + /// # Errors + /// Returns an error if the cookie results in a malformed `Set-Cookie` header. #[cfg(feature = "cookies")] pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - self.headers_mut().append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) + .map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie)) + .map_err(Into::into) } - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. + /// Add a "removal" cookie to the response that matches attributes of given cookie. + /// + /// This will cause browsers/clients to remove stored cookies with this name. + /// + /// The `Set-Cookie` header added to the response will have: + /// - name matching given cookie; + /// - domain matching given cookie; + /// - path matching given cookie; + /// - an empty value; + /// - a max-age of `0`; + /// - an expiration date far in the past. + /// + /// If the cookie you're trying to remove has an explicit path or domain set, those attributes + /// will need to be included in the cookie passed in here. + /// + /// # Errors + /// Returns an error if the given name results in a malformed `Set-Cookie` header. + #[cfg(feature = "cookies")] + pub fn add_removal_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { + let mut removal_cookie = cookie.to_owned(); + removal_cookie.make_removal(); + + HeaderValue::from_str(&removal_cookie.to_string()) + .map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie)) + .map_err(Into::into) + } + + /// Remove all cookies with the given name from this response. + /// + /// Returns the number of cookies removed. + /// + /// This method can _not_ cause a browser/client to delete any of its stored cookies. Its only + /// purpose is to delete cookies that were added to this response using [`add_cookie`] + /// and [`add_removal_cookie`]. Use [`add_removal_cookie`] to send a "removal" cookie. + /// + /// [`add_cookie`]: Self::add_cookie + /// [`add_removal_cookie`]: Self::add_removal_cookie #[cfg(feature = "cookies")] pub fn del_cookie(&mut self, name: &str) -> usize { let headers = self.headers_mut(); @@ -140,6 +176,7 @@ impl HttpResponse { headers.remove(header::SET_COOKIE); let mut count: usize = 0; + for v in vals { if let Ok(s) = v.to_str() { if let Ok(c) = Cookie::parse_encoded(s) { @@ -370,3 +407,23 @@ mod tests { assert!(dbg.contains("HttpResponse")); } } + +#[cfg(test)] +#[cfg(feature = "cookies")] +mod cookie_tests { + use super::*; + + #[test] + fn removal_cookies() { + let mut res = HttpResponse::Ok().finish(); + let cookie = Cookie::new("foo", ""); + res.add_removal_cookie(&cookie).unwrap(); + let set_cookie_hdr = res.headers().get(header::SET_COOKIE).unwrap(); + assert_eq!( + &set_cookie_hdr.as_bytes()[..25], + &b"foo=; Max-Age=0; Expires="[..], + "unexpected set-cookie value: {:?}", + set_cookie_hdr.to_str() + ); + } +} From cb5d9a7e648ad890c276a6b1879a6468b6ee6cd7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 16:58:11 +0000 Subject: [PATCH 284/861] bump deps to stable actix-server v2 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 39f2ac32a..6c64a9e87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ experimental-io-uring = ["actix-server/io-uring"] actix-codec = "0.4.1" actix-macros = "0.2.3" actix-rt = "2.6" -actix-server = "2.0.0-rc.4" +actix-server = "2" actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index b8521dd0c..993dc854e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -34,7 +34,7 @@ actix-codec = "0.4.1" actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" -actix-server = "2.0.0-rc.2" +actix-server = "2" awc = { version = "3.0.0-beta.18", default-features = false } base64 = "0.13" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 163fce931..9fecd9a57 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -80,7 +80,7 @@ zstd = { version = "0.9", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } -actix-server = "2.0.0-rc.2" +actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } actix-web = "4.0.0-beta.20" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 16c2083d8..bd9eb6cc6 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -95,7 +95,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } -actix-server = "2.0.0-rc.2" +actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" From 3dd98c308c386962cbbd0ddd0620af17c5132501 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 18:33:23 +0000 Subject: [PATCH 285/861] document Path::unprocessed panic --- actix-router/src/path.rs | 24 ++++++++++++++++++------ actix-router/src/router.rs | 1 + src/request.rs | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index fc7bb16ac..f8667ad89 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -49,7 +49,7 @@ impl Path { &mut self.path } - /// Path. + /// Returns unprocessed part of the path. #[inline] pub fn path(&self) -> &str { profile_method!(path); @@ -63,9 +63,21 @@ impl Path { } } + /// Returns unprocessed part of the path. + /// + /// # Panics + /// Unlike [`path`](Self::path), this will panic if `skip` indexes further than the path length. + #[inline] + pub fn unprocessed(&self) -> &str { + profile_method!(unprocessed); + &self.path.path()[(self.skip as usize)..] + } + /// Set new path. #[inline] pub fn set(&mut self, path: T) { + profile_method!(set); + self.skip = 0; self.path = path; self.segments.clear(); @@ -74,6 +86,8 @@ impl Path { /// Reset state. #[inline] pub fn reset(&mut self) { + profile_method!(reset); + self.skip = 0; self.segments.clear(); } @@ -81,6 +95,7 @@ impl Path { /// Skip first `n` chars in path. #[inline] pub fn skip(&mut self, n: u16) { + profile_method!(skip); self.skip += n; } @@ -102,6 +117,8 @@ impl Path { name: impl Into>, value: impl Into>, ) { + profile_method!(add_static); + self.segments .push((name.into(), PathItem::Static(value.into()))); } @@ -136,11 +153,6 @@ impl Path { None } - /// Get unprocessed part of the path - pub fn unprocessed(&self) -> &str { - &self.path.path()[(self.skip as usize)..] - } - /// Get matched parameter by name. /// /// If keyed parameter is not available empty string is used as default value. diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index 47940708e..4652ef678 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -256,6 +256,7 @@ mod tests { router.path("/name/{val}", 11); let mut router = router.finish(); + // test skip beyond path length let mut path = Path::new("/name"); path.skip(6); assert!(router.recognize_mut(&mut path).is_none()); diff --git a/src/request.rs b/src/request.rs index bcab79205..d721f2ac7 100644 --- a/src/request.rs +++ b/src/request.rs @@ -208,7 +208,7 @@ impl HttpRequest { self.resource_map().url_for(self, name, elements) } - /// Generate url for named resource + /// Generate URL for named resource /// /// This method is similar to `HttpRequest::url_for()` but it can be used /// for urls that do not contain variable parts. From 1cc3e7b24cb05a48f495a5f0e1775d51adfe148d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 20:26:33 +0000 Subject: [PATCH 286/861] deprecate `Path::path` (#2590) --- actix-files/src/path_buf.rs | 2 +- actix-files/src/service.rs | 16 +++++++++------- actix-router/CHANGES.md | 4 ++++ actix-router/src/path.rs | 34 ++++++++++++++++++++++------------ actix-router/src/resource.rs | 6 +++--- actix-router/src/url.rs | 4 ++-- src/service.rs | 16 ++++++++-------- 7 files changed, 49 insertions(+), 33 deletions(-) diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 03b2cd766..f7f7cdab6 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -85,7 +85,7 @@ impl FromRequest for PathBufWrap { type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - ready(req.match_info().path().parse()) + ready(req.match_info().unprocessed().parse()) } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 4e8b72311..5d494f878 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -120,14 +120,16 @@ impl Service for FilesService { )); } - let real_path = - match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) { - Ok(item) => item, - Err(err) => return Ok(req.error_response(err)), - }; + let path_on_disk = match PathBufWrap::parse_path( + req.match_info().unprocessed(), + this.hidden_files, + ) { + Ok(item) => item, + Err(err) => return Ok(req.error_response(err)), + }; if let Some(filter) = &this.path_filter { - if !filter(real_path.as_ref(), req.head()) { + if !filter(path_on_disk.as_ref(), req.head()) { if let Some(ref default) = this.default { return default.call(req).await; } else { @@ -137,7 +139,7 @@ impl Service for FilesService { } // full file path - let path = this.directory.join(&real_path); + let path = this.directory.join(&path_on_disk); if let Err(err) = path.canonicalize() { return this.handle_err(err, req).await; } diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index f268ffa9c..17d149b69 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +- Add `Path::as_str`. [#2590] +- Deprecate `Path::path`. [#2590] + +[#2590]: https://github.com/actix/actix-web/pull/2590 ## 0.5.0-rc.1 - 2022-01-14 diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index f8667ad89..dfb645d72 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -37,19 +37,39 @@ impl Path { } } - /// Get reference to inner path instance. + /// Returns reference to inner path instance. #[inline] pub fn get_ref(&self) -> &T { &self.path } - /// Get mutable reference to inner path instance. + /// Returns mutable reference to inner path instance. #[inline] pub fn get_mut(&mut self) -> &mut T { &mut self.path } + /// Returns full path as a string. + #[inline] + pub fn as_str(&self) -> &str { + profile_method!(as_str); + self.path.path() + } + /// Returns unprocessed part of the path. + /// + /// Returns empty string if no more is to be processed. + #[inline] + pub fn unprocessed(&self) -> &str { + profile_method!(unprocessed); + // clamp skip to path length + let skip = (self.skip as usize).min(self.as_str().len()); + &self.path.path()[skip..] + } + + /// Returns unprocessed part of the path. + #[doc(hidden)] + #[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")] #[inline] pub fn path(&self) -> &str { profile_method!(path); @@ -63,16 +83,6 @@ impl Path { } } - /// Returns unprocessed part of the path. - /// - /// # Panics - /// Unlike [`path`](Self::path), this will panic if `skip` indexes further than the path length. - #[inline] - pub fn unprocessed(&self) -> &str { - profile_method!(unprocessed); - &self.path.path()[(self.skip as usize)..] - } - /// Set new path. #[inline] pub fn set(&mut self, path: T) { diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index d39a6b923..c0b5522af 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -692,7 +692,7 @@ impl ResourceDef { let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default(); let path = resource.resource_path(); - let path_str = path.path(); + let path_str = path.unprocessed(); let (matched_len, matched_vars) = match &self.pat_type { PatternType::Static(pattern) => { @@ -710,7 +710,7 @@ impl ResourceDef { let captures = { profile_section!(pattern_dynamic_regex_exec); - match re.captures(path.path()) { + match re.captures(path.unprocessed()) { Some(captures) => captures, _ => return false, } @@ -738,7 +738,7 @@ impl ResourceDef { PatternType::DynamicSet(re, params) => { profile_section!(pattern_dynamic_set); - let path = path.path(); + let path = path.unprocessed(); let (pattern, names) = match re.matches(path).into_iter().next() { Some(idx) => ¶ms[idx], _ => return false, diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index c5a3508aa..f8d94ae4a 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -121,7 +121,7 @@ mod tests { } #[test] - fn valid_utf8_multibyte() { + fn valid_utf8_multi_byte() { let test = ('\u{FF00}'..='\u{FFFF}').collect::(); let encoded = percent_encode(test.as_bytes()); let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded)); @@ -135,6 +135,6 @@ mod tests { let path = Path::new(Url::new(uri)); // We should always get a valid utf8 string - assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok()); + assert!(String::from_utf8(path.as_str().as_bytes().to_owned()).is_ok()); } } diff --git a/src/service.rs b/src/service.rs index 03ea0b97b..162c90ec8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -198,9 +198,9 @@ impl ServiceRequest { self.req.connection_info() } - /// Get a reference to the Path parameters. + /// Returns a reference to the Path parameters. /// - /// Params is a container for url parameters. + /// Params is a container for URL parameters. /// A variable segment is specified in the form `{identifier}`, /// where the identifier can be used later in a request handler to /// access the matched value for that segment. @@ -209,6 +209,12 @@ impl ServiceRequest { self.req.match_info() } + /// Returns a mutable reference to the Path parameters. + #[inline] + pub fn match_info_mut(&mut self) -> &mut Path { + self.req.match_info_mut() + } + /// Counterpart to [`HttpRequest::match_name`]. #[inline] pub fn match_name(&self) -> Option<&str> { @@ -221,12 +227,6 @@ impl ServiceRequest { self.req.match_pattern() } - /// Get a mutable reference to the Path parameters. - #[inline] - pub fn match_info_mut(&mut self) -> &mut Path { - self.req.match_info_mut() - } - /// Get a reference to a `ResourceMap` of current application. #[inline] pub fn resource_map(&self) -> &ResourceMap { From 1bc153811822e6e0e9c89fefc6dfe394d6677c3a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 21:36:14 +0000 Subject: [PATCH 287/861] use tokio::main in client example --- actix-http/src/h1/decoder.rs | 43 ++++++++++++++++++++---------------- awc/Cargo.toml | 1 + awc/examples/client.rs | 10 ++++----- awc/src/client/connector.rs | 11 ++++----- awc/src/client/mod.rs | 5 +++-- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 3d3a3ceac..fa924f920 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -380,34 +380,36 @@ impl HeaderIndex { } #[derive(Debug, Clone, PartialEq)] -/// Http payload item +/// Chunk type yielded while decoding a payload. pub enum PayloadItem { Chunk(Bytes), Eof, } -/// Decoders to handle different Transfer-Encodings. +/// Decoder that can handle different payload types. /// -/// If a message body does not include a Transfer-Encoding, it *should* -/// include a Content-Length header. +/// If a message body does not use `Transfer-Encoding`, it should include a `Content-Length`. #[derive(Debug, Clone, PartialEq)] pub struct PayloadDecoder { kind: Kind, } impl PayloadDecoder { + /// Constructs a fixed-length payload decoder. pub fn length(x: u64) -> PayloadDecoder { PayloadDecoder { kind: Kind::Length(x), } } + /// Constructs a chunked encoding decoder. pub fn chunked() -> PayloadDecoder { PayloadDecoder { kind: Kind::Chunked(ChunkedState::Size, 0), } } + /// Creates an decoder that yields chunks until the stream returns EOF. pub fn eof() -> PayloadDecoder { PayloadDecoder { kind: Kind::Eof } } @@ -415,25 +417,26 @@ impl PayloadDecoder { #[derive(Debug, Clone, PartialEq)] enum Kind { - /// A Reader used when a Content-Length header is passed with a positive - /// integer. + /// A reader used when a `Content-Length` header is passed with a positive integer. Length(u64), - /// A Reader used when Transfer-Encoding is `chunked`. + + /// A reader used when `Transfer-Encoding` is `chunked`. Chunked(ChunkedState, u64), - /// A Reader used for responses that don't indicate a length or chunked. + + /// A reader used for responses that don't indicate a length or chunked. /// - /// Note: This should only used for `Response`s. It is illegal for a - /// `Request` to be made with both `Content-Length` and - /// `Transfer-Encoding: chunked` missing, as explained from the spec: + /// Note: This should only used for `Response`s. It is illegal for a `Request` to be made + /// without either of `Content-Length` and `Transfer-Encoding: chunked` missing, as explained + /// in [RFC 7230 §3.3.3]: /// - /// > If a Transfer-Encoding header field is present in a response and - /// > the chunked transfer coding is not the final encoding, the - /// > message body length is determined by reading the connection until - /// > it is closed by the server. If a Transfer-Encoding header field - /// > is present in a request and the chunked transfer coding is not - /// > the final encoding, the message body length cannot be determined - /// > reliably; the server MUST respond with the 400 (Bad Request) - /// > status code and then close the connection. + /// > If a Transfer-Encoding header field is present in a response and the chunked transfer + /// > coding is not the final encoding, the message body length is determined by reading the + /// > connection until it is closed by the server. If a Transfer-Encoding header field is + /// > present in a request and the chunked transfer coding is not the final encoding, the + /// > message body length cannot be determined reliably; the server MUST respond with the 400 + /// > (Bad Request) status code and then close the connection. + /// + /// [RFC 7230 §3.3.3]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 Eof, } @@ -463,6 +466,7 @@ impl Decoder for PayloadDecoder { Ok(Some(PayloadItem::Chunk(buf))) } } + Kind::Chunked(ref mut state, ref mut size) => { loop { let mut buf = None; @@ -488,6 +492,7 @@ impl Decoder for PayloadDecoder { } } } + Kind::Eof => { if src.is_empty() { Ok(None) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bd9eb6cc6..0bce1b21f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -109,6 +109,7 @@ futures-util = { version = "0.3.7", default-features = false } static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" +tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } zstd = "0.9" [[example]] diff --git a/awc/examples/client.rs b/awc/examples/client.rs index 653cb226f..16ad330b8 100644 --- a/awc/examples/client.rs +++ b/awc/examples/client.rs @@ -1,13 +1,13 @@ use std::error::Error as StdError; -#[actix_web::main] +#[tokio::main] async fn main() -> Result<(), Box> { - std::env::set_var("RUST_LOG", "client=trace,awc=trace,actix_http=trace"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + // construct request builder let client = awc::Client::new(); - // Create request builder, configure request and send + // configure request let request = client .get("https://www.rust-lang.org/") .append_header(("User-Agent", "Actix-web")); @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box> { let mut response = request.send().await?; - // server http response + // server response head println!("Response: {:?}", response); // read response body diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 423f656a8..26c62b924 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -207,7 +207,7 @@ where self } - /// Maximum supported HTTP major version. + /// Sets maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. pub fn max_http_version(mut self, val: http::Version) -> Self { @@ -222,8 +222,8 @@ where self } - /// Indicates the initial window size (in octets) for - /// HTTP2 stream-level flow control for received data. + /// Sets the initial window size (in octets) for HTTP/2 stream-level flow control for + /// received data. /// /// The default value is 65,535 and is good for APIs, but not for big objects. pub fn initial_window_size(mut self, size: u32) -> Self { @@ -231,8 +231,8 @@ where self } - /// Indicates the initial window size (in octets) for - /// HTTP2 connection-level flow control for received data. + /// Sets the initial window size (in octets) for HTTP/2 connection-level flow control for + /// received data. /// /// The default value is 65,535 and is good for APIs, but not for big objects. pub fn initial_connection_window_size(mut self, size: u32) -> Self { @@ -243,6 +243,7 @@ where /// Set total number of simultaneous connections per type of scheme. /// /// If limit is 0, the connector has no limit. + /// /// The default limit size is 100. pub fn limit(mut self, limit: usize) -> Self { self.config.limit = limit; diff --git a/awc/src/client/mod.rs b/awc/src/client/mod.rs index d5854d83e..443bf1239 100644 --- a/awc/src/client/mod.rs +++ b/awc/src/client/mod.rs @@ -67,12 +67,13 @@ impl Default for Client { } impl Client { - /// Create new client instance with default settings. + /// Constructs new client instance with default settings. pub fn new() -> Client { Client::default() } - /// Create `Client` builder. + /// Constructs new `Client` builder. + /// /// This function is equivalent of `ClientBuilder::new()`. pub fn builder() -> ClientBuilder< impl Service< From 81ef12a0fd0b982a43e120f2c0afc1b65772a189 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 19 Jan 2022 22:23:53 +0000 Subject: [PATCH 288/861] add warn log to from_parts if given request is cloned closes #2562 --- actix-web-codegen/src/route.rs | 14 +++++++------- src/request.rs | 4 ++++ src/service.rs | 32 +++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index a4472efd2..cb1ba1ef6 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -302,13 +302,13 @@ impl ToTokens for Route { if methods.len() > 1 { quote! { .guard( - actix_web::guard::Any(actix_web::guard::#first()) - #(.or(actix_web::guard::#others()))* + ::actix_web::guard::Any(::actix_web::guard::#first()) + #(.or(::actix_web::guard::#others()))* ) } } else { quote! { - .guard(actix_web::guard::#first()) + .guard(::actix_web::guard::#first()) } } }; @@ -318,17 +318,17 @@ impl ToTokens for Route { #[allow(non_camel_case_types, missing_docs)] pub struct #name; - impl actix_web::dev::HttpServiceFactory for #name { + impl ::actix_web::dev::HttpServiceFactory for #name { fn register(self, __config: &mut actix_web::dev::AppService) { #ast - let __resource = actix_web::Resource::new(#path) + let __resource = ::actix_web::Resource::new(#path) .name(#resource_name) #method_guards - #(.guard(actix_web::guard::fn_guard(#guards)))* + #(.guard(::actix_web::guard::fn_guard(#guards)))* #(.wrap(#wrappers))* .#resource_type(#name); - actix_web::dev::HttpServiceFactory::register(__resource, __config) + ::actix_web::dev::HttpServiceFactory::register(__resource, __config) } } }; diff --git a/src/request.rs b/src/request.rs index d721f2ac7..63db56fd3 100644 --- a/src/request.rs +++ b/src/request.rs @@ -138,6 +138,10 @@ impl HttpRequest { &self.inner.path } + /// Returns a mutable reference to the URL parameters container. + /// + /// # Panics + /// Panics if this `HttpRequest` has been cloned. #[inline] pub(crate) fn match_info_mut(&mut self) -> &mut Path { &mut Rc::get_mut(&mut self.inner).unwrap().path diff --git a/src/service.rs b/src/service.rs index 162c90ec8..81424a654 100644 --- a/src/service.rs +++ b/src/service.rs @@ -97,6 +97,11 @@ impl ServiceRequest { /// Construct request from parts. pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { + #[cfg(debug_assertions)] + if Rc::strong_count(&req.inner) > 1 { + log::warn!("Cloning an `HttpRequest` might cause panics."); + } + Self { req, payload } } @@ -663,7 +668,7 @@ service_tuple! { A B C D E F G H I J K L } #[cfg(test)] mod tests { use super::*; - use crate::test::{init_service, TestRequest}; + use crate::test::{self, init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; use actix_service::Service; use actix_utils::future::ok; @@ -810,4 +815,29 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); } + + #[actix_rt::test] + #[should_panic(expected = "called `Option::unwrap()` on a `None` value")] + async fn cloning_request_panics() { + async fn index(_name: web::Path<(String,)>) -> &'static str { + "" + } + + let app = test::init_service( + App::new() + .wrap_fn(|req, svc| { + let (req, pl) = req.into_parts(); + let _req2 = req.clone(); + let req = ServiceRequest::from_parts(req, pl); + svc.call(req) + }) + .service( + web::resource("/resource1/{name}/index.html").route(web::get().to(index)), + ), + ) + .await; + + let req = test::TestRequest::default().to_request(); + let _res = test::call_service(&app, req).await; + } } From f2e736719a19baae32a52cb6b3fd3c0c424b569e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 20 Jan 2022 01:25:46 +0000 Subject: [PATCH 289/861] add url_for test for conflicting named resources --- actix-web-codegen/src/lib.rs | 12 +++--- src/config.rs | 10 ++++- src/request.rs | 52 ++++++++++++++++++++-- src/rmap.rs | 83 ++++++++++++++++++++++++++++++++---- src/service.rs | 1 + 5 files changed, 139 insertions(+), 19 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 52cfc0d8f..480fd2e4b 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -39,7 +39,7 @@ //! ``` //! # use actix_web::HttpResponse; //! # use actix_web_codegen::route; -//! #[route("/test", method="GET", method="HEAD")] +//! #[route("/test", method = "GET", method = "HEAD")] //! async fn get_and_head_handler() -> HttpResponse { //! HttpResponse::Ok().finish() //! } @@ -74,10 +74,12 @@ mod route; /// /// # Attributes /// - `"path"` - Raw literal string with path for which to register handler. -/// - `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used. -/// - `method="HTTP_METHOD"` - Registers HTTP method to provide guard for. Upper-case string, "GET", "POST" for example. -/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` -/// - `wrap="Middleware"` - Registers a resource middleware. +/// - `name = "resource_name"` - Specifies resource name for the handler. If not set, the function +/// name of handler is used. +/// - `method = "HTTP_METHOD"` - Registers HTTP method to provide guard for. Upper-case string, +/// "GET", "POST" for example. +/// - `guard = "function_name"` - Registers function as guard using `actix_web::guard::fn_guard` +/// - `wrap = "Middleware"` - Registers a resource middleware. /// /// # Notes /// Function name can be specified as any expression that is going to be accessible to the generate diff --git a/src/config.rs b/src/config.rs index 77fba18ed..9fe75df97 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,8 +102,14 @@ impl AppService { InitError = (), > + 'static, { - self.services - .push((rdef, boxed::factory(factory.into_factory()), guards, nested)); + dbg!(rdef.pattern()); + + self.services.push(( + rdef, + boxed::factory(factory.into_factory()), + guards, + dbg!(nested), + )); } } diff --git a/src/request.rs b/src/request.rs index 63db56fd3..61b820950 100644 --- a/src/request.rs +++ b/src/request.rs @@ -508,10 +508,12 @@ mod tests { use bytes::Bytes; use super::*; - use crate::dev::{ResourceDef, ResourceMap}; - use crate::http::{header, StatusCode}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::{ResourceDef, ResourceMap}, + http::{header, StatusCode}, + test::{self, call_service, init_service, read_body, TestRequest}, + web, App, HttpResponse, + }; #[test] fn test_debug() { @@ -865,4 +867,46 @@ mod tests { let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn url_for_closest_named_resource() { + // we mount the route named 'nested' on 2 different scopes, 'a' and 'b' + let srv = test::init_service( + App::new() + .service( + web::scope("/foo") + .service(web::resource("/nested").name("nested").route(web::get().to( + |req: HttpRequest| { + HttpResponse::Ok() + .body(format!("{}", req.url_for_static("nested").unwrap())) + }, + ))) + .service(web::scope("/baz").service(web::resource("deep"))) + .service(web::resource("{foo_param}")), + ) + .service(web::scope("/bar").service( + web::resource("/nested").name("nested").route(web::get().to( + |req: HttpRequest| { + HttpResponse::Ok() + .body(format!("{}", req.url_for_static("nested").unwrap())) + }, + )), + )), + ) + .await; + + let foo_resp = + test::call_service(&srv, TestRequest::with_uri("/foo/nested").to_request()).await; + assert_eq!(foo_resp.status(), StatusCode::OK); + let body = read_body(foo_resp).await; + // XXX: body equals http://localhost:8080/bar/nested + // because nested from /bar overrides /foo's + assert_eq!(body, "http://localhost:8080/bar/nested"); + + let bar_resp = + test::call_service(&srv, TestRequest::with_uri("/bar/nested").to_request()).await; + assert_eq!(bar_resp.status(), StatusCode::OK); + let body = read_body(bar_resp).await; + assert_eq!(body, "http://localhost:8080/bar/nested"); + } } diff --git a/src/rmap.rs b/src/rmap.rs index 432eaf83c..932f7acde 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -1,6 +1,7 @@ use std::{ borrow::Cow, cell::RefCell, + fmt::Write as _, rc::{Rc, Weak}, }; @@ -10,12 +11,14 @@ use url::Url; use crate::{error::UrlGenerationError, request::HttpRequest}; +const AVG_PATH_LEN: usize = 24; + #[derive(Clone, Debug)] pub struct ResourceMap { pattern: ResourceDef, - /// Named resources within the tree or, for external resources, - /// it points to isolated nodes outside the tree. + /// Named resources within the tree or, for external resources, it points to isolated nodes + /// outside the tree. named: AHashMap>, parent: RefCell>, @@ -35,6 +38,35 @@ impl ResourceMap { } } + /// Format resource map as tree structure (unfinished). + #[allow(dead_code)] + pub(crate) fn tree(&self) -> String { + let mut buf = String::new(); + self._tree(&mut buf, 0); + buf + } + + pub(crate) fn _tree(&self, buf: &mut String, level: usize) { + if let Some(children) = &self.nodes { + for child in children { + writeln!( + buf, + "{}{} {}", + "--".repeat(level), + child.pattern.pattern().unwrap(), + child + .pattern + .name() + .map(|name| format!("({})", name)) + .unwrap_or_else(|| "".to_owned()) + ) + .unwrap(); + + ResourceMap::_tree(child, buf, level + 1); + } + } + } + /// Adds a (possibly nested) resource. /// /// To add a non-prefix pattern, `nested` must be `None`. @@ -44,7 +76,11 @@ impl ResourceMap { pattern.set_id(self.nodes.as_ref().unwrap().len() as u16); if let Some(new_node) = nested { - assert_eq!(&new_node.pattern, pattern, "`patern` and `nested` mismatch"); + debug_assert_eq!( + &new_node.pattern, pattern, + "`pattern` and `nested` mismatch" + ); + // parents absorb references to the named resources of children self.named.extend(new_node.named.clone().into_iter()); self.nodes.as_mut().unwrap().push(new_node); } else { @@ -64,7 +100,7 @@ impl ResourceMap { None => false, }; - // Don't add external resources to the tree + // don't add external resources to the tree if !is_external { self.nodes.as_mut().unwrap().push(new_node); } @@ -78,7 +114,7 @@ impl ResourceMap { } } - /// Generate url for named resource + /// Generate URL for named resource. /// /// Check [`HttpRequest::url_for`] for detailed information. pub fn url_for( @@ -97,7 +133,7 @@ impl ResourceMap { .named .get(name) .ok_or(UrlGenerationError::ResourceNotFound)? - .root_rmap_fn(String::with_capacity(24), |mut acc, node| { + .root_rmap_fn(String::with_capacity(AVG_PATH_LEN), |mut acc, node| { node.pattern .resource_path_from_iter(&mut acc, &mut elements) .then(|| acc) @@ -128,6 +164,7 @@ impl ResourceMap { Ok(url) } + /// Returns true if there is a resource that would match `path`. pub fn has_resource(&self, path: &str) -> bool { self.find_matching_node(path).is_some() } @@ -142,9 +179,10 @@ impl ResourceMap { /// is possible. pub fn match_pattern(&self, path: &str) -> Option { self.find_matching_node(path)?.root_rmap_fn( - String::with_capacity(24), + String::with_capacity(AVG_PATH_LEN), |mut acc, node| { - acc.push_str(node.pattern.pattern()?); + let pattern = node.pattern.pattern()?; + acc.push_str(pattern); Some(acc) }, ) @@ -490,4 +528,33 @@ mod tests { "https://duck.com/abcd" ); } + + #[test] + fn url_for_override_within_map() { + let mut root = ResourceMap::new(ResourceDef::prefix("")); + + let mut foo_rdef = ResourceDef::prefix("/foo"); + let mut foo_map = ResourceMap::new(foo_rdef.clone()); + let mut nested_rdef = ResourceDef::new("/nested"); + nested_rdef.set_name("nested"); + foo_map.add(&mut nested_rdef, None); + root.add(&mut foo_rdef, Some(Rc::new(foo_map))); + + let mut foo_rdef = ResourceDef::prefix("/bar"); + let mut foo_map = ResourceMap::new(foo_rdef.clone()); + let mut nested_rdef = ResourceDef::new("/nested"); + nested_rdef.set_name("nested"); + foo_map.add(&mut nested_rdef, None); + root.add(&mut foo_rdef, Some(Rc::new(foo_map))); + + let rmap = Rc::new(root); + ResourceMap::finish(&rmap); + + let req = crate::test::TestRequest::default().to_http_request(); + + let url = rmap.url_for(&req, "nested", &[""; 0]).unwrap().to_string(); + assert_eq!(url, "http://localhost:8080/bar/nested"); + + assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); + } } diff --git a/src/service.rs b/src/service.rs index 81424a654..061c3e044 100644 --- a/src/service.rs +++ b/src/service.rs @@ -831,6 +831,7 @@ mod tests { let req = ServiceRequest::from_parts(req, pl); svc.call(req) }) + .route("/", web::get().to(|| async { "" })) .service( web::resource("/resource1/{name}/index.html").route(web::get().to(index)), ), From 68ad81f9891fa95252755580dac1da9169035f56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 20 Jan 2022 01:30:05 +0000 Subject: [PATCH 290/861] remove debug logs --- src/config.rs | 10 ++-------- src/middleware/logger.rs | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9fe75df97..77fba18ed 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,14 +102,8 @@ impl AppService { InitError = (), > + 'static, { - dbg!(rdef.pattern()); - - self.services.push(( - rdef, - boxed::factory(factory.into_factory()), - guards, - dbg!(nested), - )); + self.services + .push((rdef, boxed::factory(factory.into_factory()), guards, nested)); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 969cb0c10..63055ecba 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -700,7 +700,6 @@ mod tests { Ok(()) }; let s = format!("{}", FormatDisplay(&render)); - println!("{}", s); assert!(s.contains("/test/route/yeah")); } @@ -794,7 +793,6 @@ mod tests { Ok(()) }; let s = format!("{}", FormatDisplay(&render)); - println!("{}", s); assert!(s.contains("192.0.2.60")); } From f227e880d78b6f04ea0c57d588c1ed306ba1eed7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 20 Jan 2022 01:53:02 +0000 Subject: [PATCH 291/861] refactor route codegen to be cleaner --- actix-web-codegen/src/lib.rs | 124 ++++++++++++++++------------------- src/request.rs | 3 +- 2 files changed, 59 insertions(+), 68 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 480fd2e4b..38e3cc379 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -45,7 +45,12 @@ //! } //! ``` //! -//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes +//! # Multiple Path Handlers +//! There are no macros to generate multi-path handlers. Let us know in [this issue]. +//! +//! [this issue]: https://github.com/actix/actix-web/issues/1709 +//! +//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes //! [GET]: macro@get //! [POST]: macro@post //! [PUT]: macro@put @@ -73,24 +78,23 @@ mod route; /// ``` /// /// # Attributes -/// - `"path"` - Raw literal string with path for which to register handler. -/// - `name = "resource_name"` - Specifies resource name for the handler. If not set, the function +/// - `"path"`: Raw literal string with path for which to register handler. +/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function /// name of handler is used. -/// - `method = "HTTP_METHOD"` - Registers HTTP method to provide guard for. Upper-case string, +/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string, /// "GET", "POST" for example. -/// - `guard = "function_name"` - Registers function as guard using `actix_web::guard::fn_guard` -/// - `wrap = "Middleware"` - Registers a resource middleware. +/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`. +/// - `wrap = "Middleware"`: Registers a resource middleware. /// /// # Notes /// Function name can be specified as any expression that is going to be accessible to the generate /// code, e.g `my_guard` or `my_module::my_guard`. /// -/// # Example -/// +/// # Examples /// ``` /// # use actix_web::HttpResponse; /// # use actix_web_codegen::route; -/// #[route("/test", method="GET", method="HEAD")] +/// #[route("/test", method = "GET", method = "HEAD")] /// async fn example() -> HttpResponse { /// HttpResponse::Ok().finish() /// } @@ -100,69 +104,55 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { route::with_method(None, args, input) } -macro_rules! doc_comment { - ($x:expr; $($tt:tt)*) => { - #[doc = $x] - $($tt)* - }; -} - macro_rules! method_macro { - ( - $($variant:ident, $method:ident,)+ - ) => { - $(doc_comment! { -concat!(" -Creates route handler with `actix_web::guard::", stringify!($variant), "`. - -# Syntax -```plain -#[", stringify!($method), r#"("path"[, attributes])] -``` - -# Attributes -- `"path"` - Raw literal string with path for which to register handler. -- `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used. -- `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`. -- `wrap="Middleware"` - Registers a resource middleware. - -# Notes -Function name can be specified as any expression that is going to be accessible to the generate -code, e.g `my_guard` or `my_module::my_guard`. - -# Example - -``` -# use actix_web::HttpResponse; -# use actix_web_codegen::"#, stringify!($method), "; -#[", stringify!($method), r#"("/")] -async fn example() -> HttpResponse { - HttpResponse::Ok().finish() -} -``` -"#); - #[proc_macro_attribute] - pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream { - route::with_method(Some(route::MethodType::$variant), args, input) - } - })+ + ($variant:ident, $method:ident) => { + #[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] + /// + /// # Syntax + /// ```plain + #[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)] + /// ``` + /// + /// # Attributes + /// - `"path"`: Raw literal string with path for which to register handler. + /// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the + /// function name of handler is used. + /// - `guard = "function_name"`: Registers function as guard. + /// using `actix_web::guard::fn_guard`. + /// - `wrap = "Middleware"`: Registers a resource middleware. + /// + /// # Notes + /// Function name can be specified as any expression that is going to be accessible to the generate + /// code, e.g `my_guard` or `my_module::my_guard`. + /// + /// # Example + /// ``` + /// # use actix_web::HttpResponse; + #[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")] + #[doc = concat!("#[", stringify!($method), r#"("/")]"#)] + /// async fn example() -> HttpResponse { + /// HttpResponse::Ok().finish() + /// } + /// ``` + #[proc_macro_attribute] + pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream { + route::with_method(Some(route::MethodType::$variant), args, input) + } }; } -method_macro! { - Get, get, - Post, post, - Put, put, - Delete, delete, - Head, head, - Connect, connect, - Options, options, - Trace, trace, - Patch, patch, -} - -/// Marks async main function as the actix system entry-point. +method_macro!(Get, get); +method_macro!(Post, post); +method_macro!(Put, put); +method_macro!(Delete, delete); +method_macro!(Head, head); +method_macro!(Connect, connect); +method_macro!(Options, options); +method_macro!(Trace, trace); +method_macro!(Patch, patch); +/// Marks async main function as the Actix Web system entry-point. +/// /// # Examples /// ``` /// #[actix_web::main] diff --git a/src/request.rs b/src/request.rs index 61b820950..b3db5ffbd 100644 --- a/src/request.rs +++ b/src/request.rs @@ -899,8 +899,9 @@ mod tests { test::call_service(&srv, TestRequest::with_uri("/foo/nested").to_request()).await; assert_eq!(foo_resp.status(), StatusCode::OK); let body = read_body(foo_resp).await; - // XXX: body equals http://localhost:8080/bar/nested + // `body` equals http://localhost:8080/bar/nested // because nested from /bar overrides /foo's + // to do this any other way would require something like a custom tree search assert_eq!(body, "http://localhost:8080/bar/nested"); let bar_resp = From c9599163466c070a0b42cf6e8836c78b3636dda2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 20 Jan 2022 01:54:57 +0000 Subject: [PATCH 292/861] fmt codegen --- actix-web-codegen/src/lib.rs | 63 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 38e3cc379..79ed342d2 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -106,38 +106,37 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { macro_rules! method_macro { ($variant:ident, $method:ident) => { - #[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] - /// - /// # Syntax - /// ```plain - #[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)] - /// ``` - /// - /// # Attributes - /// - `"path"`: Raw literal string with path for which to register handler. - /// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the - /// function name of handler is used. - /// - `guard = "function_name"`: Registers function as guard. - /// using `actix_web::guard::fn_guard`. - /// - `wrap = "Middleware"`: Registers a resource middleware. - /// - /// # Notes - /// Function name can be specified as any expression that is going to be accessible to the generate - /// code, e.g `my_guard` or `my_module::my_guard`. - /// - /// # Example - /// ``` - /// # use actix_web::HttpResponse; - #[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")] - #[doc = concat!("#[", stringify!($method), r#"("/")]"#)] - /// async fn example() -> HttpResponse { - /// HttpResponse::Ok().finish() - /// } - /// ``` - #[proc_macro_attribute] - pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream { - route::with_method(Some(route::MethodType::$variant), args, input) - } +#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] +/// +/// # Syntax +/// ```plain +#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)] +/// ``` +/// +/// # Attributes +/// - `"path"`: Raw literal string with path for which to register handler. +/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function +/// name of handler is used. +/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`. +/// - `wrap = "Middleware"`: Registers a resource middleware. +/// +/// # Notes +/// Function name can be specified as any expression that is going to be accessible to the +/// generate code, e.g `my_guard` or `my_module::my_guard`. +/// +/// # Example +/// ``` +/// # use actix_web::HttpResponse; +#[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")] +#[doc = concat!("#[", stringify!($method), r#"("/")]"#)] +/// async fn example() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// ``` +#[proc_macro_attribute] +pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream { + route::with_method(Some(route::MethodType::$variant), args, input) +} }; } From bc89f0bfc23a0bb7978fc128b947c56f13fa3d03 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 16:56:33 +0000 Subject: [PATCH 293/861] s/example/examples --- actix-files/src/lib.rs | 2 +- actix-web-codegen/src/lib.rs | 2 +- awc/src/ws.rs | 2 +- examples/basic.rs | 2 +- src/http/header/date.rs | 2 +- src/http/header/expires.rs | 2 +- src/http/header/if_modified_since.rs | 2 +- src/http/header/if_unmodified_since.rs | 2 +- src/http/header/last_modified.rs | 2 +- src/middleware/logger.rs | 2 +- src/request_data.rs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index af404721c..43b06a858 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -2,7 +2,7 @@ //! //! Provides a non-blocking service for serving static files from disk. //! -//! # Example +//! # Examples //! ``` //! use actix_web::App; //! use actix_files::Files; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 79ed342d2..f41e1ce38 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -124,7 +124,7 @@ macro_rules! method_macro { /// Function name can be specified as any expression that is going to be accessible to the /// generate code, e.g `my_guard` or `my_module::my_guard`. /// -/// # Example +/// # Examples /// ``` /// # use actix_web::HttpResponse; #[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")] diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f3ee02d43..d8ed4c879 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -2,7 +2,7 @@ //! //! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client. //! -//! # Example +//! # Examples //! //! ```no_run //! use awc::{Client, ws}; diff --git a/examples/basic.rs b/examples/basic.rs index 598d13a40..494470676 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -24,7 +24,7 @@ async fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) + .wrap(middleware::Logger::default().log_target("1234")) .service(index) .service(no_params) .service( diff --git a/src/http/header/date.rs b/src/http/header/date.rs index 4063deab1..f62740211 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -16,7 +16,7 @@ crate::http::header::common_header! { /// # Example Values /// * `Tue, 15 Nov 1994 08:12:31 GMT` /// - /// # Example + /// # Examples /// /// ``` /// use std::time::SystemTime; diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 5b6c65c53..55fe5acc5 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -19,7 +19,7 @@ crate::http::header::common_header! { /// # Example Values /// * `Thu, 01 Dec 1994 16:00:00 GMT` /// - /// # Example + /// # Examples /// /// ``` /// use std::time::{SystemTime, Duration}; diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index 14d6c3553..897210944 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -18,7 +18,7 @@ crate::http::header::common_header! { /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// - /// # Example + /// # Examples /// /// ``` /// use std::time::{SystemTime, Duration}; diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 0df6d7ba0..2ee3160b4 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -18,7 +18,7 @@ crate::http::header::common_header! { /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// - /// # Example + /// # Examples /// /// ``` /// use std::time::{SystemTime, Duration}; diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index e15443ed1..59e649bea 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -17,7 +17,7 @@ crate::http::header::common_header! { /// # Example Values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// - /// # Example + /// # Examples /// /// ``` /// use std::time::{SystemTime, Duration}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 63055ecba..d68e1a122 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -124,7 +124,7 @@ impl Logger { /// /// It is convention to print "-" to indicate no output instead of an empty string. /// - /// # Example + /// # Examples /// ``` /// # use actix_web::http::{header::HeaderValue}; /// # use actix_web::middleware::Logger; diff --git a/src/request_data.rs b/src/request_data.rs index 68103a7e9..719e6551f 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -24,7 +24,7 @@ use crate::{ /// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not /// provided to make this potential foot-gun more obvious. /// -/// # Example +/// # Examples /// ```no_run /// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _}; /// From ae7f71e317d40a4ebe58621a2695f0af45dfe947 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 17:18:07 +0000 Subject: [PATCH 294/861] remove ambiguous `HttpResponseBuilder::del_cookie` (#2591) --- CHANGES.md | 2 + Cargo.toml | 4 ++ src/response/builder.rs | 120 ++++++++++++++++------------------------ tests/test_server.rs | 12 ++-- 4 files changed, 59 insertions(+), 79 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fae671072..44bbc30f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,9 +6,11 @@ ### Removed - `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] +- `HttpRequestBuilder::del_cookie`. [#2591] [#2585]: https://github.com/actix/actix-web/pull/2585 [#2586]: https://github.com/actix/actix-web/pull/2586 +[#2591]: https://github.com/actix/actix-web/pull/2591 ## 4.0.0-beta.20 - 2022-01-14 diff --git a/Cargo.toml b/Cargo.toml index 6c64a9e87..ce7eaeb61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,10 @@ awc = { path = "awc" } name = "test_server" required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] +[[test]] +name = "compression" +required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] + [[example]] name = "basic" required-features = ["compress-gzip"] diff --git a/src/response/builder.rs b/src/response/builder.rs index c8e44729a..f50aad9f4 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -6,18 +6,17 @@ use std::{ task::{Context, Poll}, }; -use actix_http::{ - error::HttpError, - header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue}, - ConnectionType, Extensions, Response, ResponseHead, StatusCode, -}; +use actix_http::{error::HttpError, Response, ResponseHead}; use bytes::Bytes; use futures_core::Stream; use serde::Serialize; use crate::{ body::{BodyStream, BoxBody, MessageBody}, + dev::Extensions, error::{Error, JsonPayloadError}, + http::header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue}, + http::{ConnectionType, StatusCode}, BoxError, HttpRequest, HttpResponse, Responder, }; @@ -26,9 +25,7 @@ use crate::{ /// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct HttpResponseBuilder { res: Option>, - err: Option, - #[cfg(feature = "cookies")] - cookies: Option, + error: Option, } impl HttpResponseBuilder { @@ -37,9 +34,7 @@ impl HttpResponseBuilder { pub fn new(status: StatusCode) -> Self { Self { res: Some(Response::with_body(status, BoxBody::new(()))), - err: None, - #[cfg(feature = "cookies")] - cookies: None, + error: None, } } @@ -68,7 +63,7 @@ impl HttpResponseBuilder { Ok((key, value)) => { parts.headers.insert(key, value); } - Err(e) => self.err = Some(e.into()), + Err(e) => self.error = Some(e.into()), }; } @@ -90,7 +85,7 @@ impl HttpResponseBuilder { if let Some(parts) = self.inner() { match header.try_into_pair() { Ok((key, value)) => parts.headers.append(key, value), - Err(e) => self.err = Some(e.into()), + Err(e) => self.error = Some(e.into()), }; } @@ -109,14 +104,14 @@ impl HttpResponseBuilder { K::Error: Into, V: TryIntoHeaderValue, { - if self.err.is_some() { + if self.error.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()), + (Err(err), _) => self.error = Some(err.into()), + (_, Err(err)) => self.error = Some(err.into()), } self @@ -134,14 +129,14 @@ impl HttpResponseBuilder { K::Error: Into, V: TryIntoHeaderValue, { - if self.err.is_some() { + if self.error.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()), + (Err(err), _) => self.error = Some(err.into()), + (_, Err(err)) => self.error = Some(err.into()), } self @@ -214,18 +209,23 @@ impl HttpResponseBuilder { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); } - Err(e) => self.err = Some(e.into()), + Err(e) => self.error = Some(e.into()), }; } self } - /// Set a cookie. + /// Add a cookie to the response. /// + /// To send a "removal" cookie, call [`.make_removal()`](cookie::Cookie::make_removal) on the + /// given cookie. See [`HttpResponse::add_removal_cookie()`] to learn more. + /// + /// # Examples + /// Send a new cookie: /// ``` /// use actix_web::{HttpResponse, cookie::Cookie}; /// - /// HttpResponse::Ok() + /// let res = HttpResponse::Ok() /// .cookie( /// Cookie::build("name", "value") /// .domain("www.rust-lang.org") @@ -236,45 +236,31 @@ impl HttpResponseBuilder { /// ) /// .finish(); /// ``` - #[cfg(feature = "cookies")] - pub fn cookie<'c>(&mut self, cookie: cookie::Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = cookie::CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Remove cookie. - /// - /// A `Set-Cookie` header is added that will delete a cookie with the same name from the client. /// + /// Send a removal cookie: /// ``` - /// use actix_web::{HttpRequest, HttpResponse, Responder}; + /// use actix_web::{HttpResponse, cookie::Cookie}; /// - /// async fn handler(req: HttpRequest) -> impl Responder { - /// let mut builder = HttpResponse::Ok(); + /// // the name, domain and path match the cookie created in the previous example + /// let mut cookie = Cookie::build("name", "value-does-not-matter") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .finish(); + /// cookie.make_removal(); /// - /// if let Some(ref cookie) = req.cookie("name") { - /// builder.del_cookie(cookie); - /// } - /// - /// builder.finish() - /// } + /// let res = HttpResponse::Ok() + /// .cookie(cookie) + /// .finish(); /// ``` #[cfg(feature = "cookies")] - pub fn del_cookie(&mut self, cookie: &cookie::Cookie<'_>) -> &mut Self { - if self.cookies.is_none() { - self.cookies = Some(cookie::CookieJar::new()) + pub fn cookie(&mut self, cookie: cookie::Cookie<'_>) -> &mut Self { + match cookie.to_string().try_into_value() { + Ok(hdr_val) => self.append_header((header::SET_COOKIE, hdr_val)), + Err(err) => { + self.error = Some(err.into()); + self + } } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); - self } /// Returns a reference to the response-local data/extensions container. @@ -297,6 +283,9 @@ impl HttpResponseBuilder { /// Set a body and build the `HttpResponse`. /// + /// Unlike [`message_body`](Self::message_body), errors are converted into error + /// responses immediately. + /// /// `HttpResponseBuilder` can not be used after this call. pub fn body(&mut self, body: B) -> HttpResponse where @@ -312,7 +301,7 @@ impl HttpResponseBuilder { /// /// `HttpResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> Result, Error> { - if let Some(err) = self.err.take() { + if let Some(err) = self.error.take() { return Err(err.into()); } @@ -322,20 +311,7 @@ impl HttpResponseBuilder { .expect("cannot reuse response builder") .set_body(body); - #[allow(unused_mut)] // mut is only unused when cookies are disabled - let mut res = HttpResponse::from(res); - - #[cfg(feature = "cookies")] - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match actix_http::header::HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => res.headers_mut().append(header::SET_COOKIE, val), - Err(err) => return Err(err.into()), - }; - } - } - - Ok(res) + Ok(HttpResponse::from(res)) } /// Set a streaming body and build the `HttpResponse`. @@ -384,14 +360,12 @@ impl HttpResponseBuilder { pub fn take(&mut self) -> Self { Self { res: self.res.take(), - err: self.err.take(), - #[cfg(feature = "cookies")] - cookies: self.cookies.take(), + error: self.error.take(), } } fn inner(&mut self) -> Option<&mut ResponseHead> { - if self.err.is_some() { + if self.error.is_some() { return None; } diff --git a/tests/test_server.rs b/tests/test_server.rs index 987e51a65..ade48a485 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,7 +11,7 @@ use std::{ }; use actix_web::{ - cookie::{Cookie, CookieBuilder}, + cookie::Cookie, http::{header, StatusCode}, middleware::{Compress, NormalizePath, TrailingSlash}, web, App, Error, HttpResponse, @@ -773,7 +773,7 @@ async fn test_server_cookies() { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( - CookieBuilder::new("first", "first_value") + Cookie::build("first", "first_value") .http_only(true) .finish(), ) @@ -787,13 +787,13 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = CookieBuilder::new("first", "first_value") + let first_cookie = Cookie::build("first", "first_value") .http_only(true) .finish(); - let second_cookie = Cookie::new("second", "second_value"); + let second_cookie = Cookie::new("second", "first_value"); let cookies = res.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 2); + assert_eq!(cookies.len(), 3); if cookies[0] == first_cookie { assert_eq!(cookies[1], second_cookie); } else { @@ -809,7 +809,7 @@ async fn test_server_cookies() { .get_all(http::header::SET_COOKIE) .map(|header| header.to_str().expect("To str").to_string()) .collect::>(); - assert_eq!(cookies.len(), 2); + assert_eq!(cookies.len(), 3); if cookies[0] == first_cookie { assert_eq!(cookies[1], second_cookie); } else { From cb7347216ce1d715fd4fa6c3e5d348038595b06f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 17:18:39 +0000 Subject: [PATCH 295/861] add `Logger::log_target` (#2594) --- CHANGES.md | 4 +++- examples/basic.rs | 2 +- src/middleware/logger.rs | 43 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 44bbc30f9..1837aa577 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,8 @@ ## Unreleased - 2021-xx-xx ### Added -- `HttpResponse::add_removal_cookie` [#2586] +- `HttpResponse::add_removal_cookie`. [#2586] +- `Logger::log_target`. [#2594] ### Removed - `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] @@ -11,6 +12,7 @@ [#2585]: https://github.com/actix/actix-web/pull/2585 [#2586]: https://github.com/actix/actix-web/pull/2586 [#2591]: https://github.com/actix/actix-web/pull/2591 +[#2594]: https://github.com/actix/actix-web/pull/2594 ## 4.0.0-beta.20 - 2022-01-14 diff --git a/examples/basic.rs b/examples/basic.rs index 494470676..36b1cdd8f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -24,7 +24,7 @@ async fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default().log_target("1234")) + .wrap(middleware::Logger::default().log_target("http_log")) .service(index) .service(no_params) .service( diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d68e1a122..53a3550de 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -1,6 +1,7 @@ //! For middleware documentation, see [`Logger`]. use std::{ + borrow::Cow, collections::HashSet, convert::TryFrom, env, @@ -87,6 +88,7 @@ struct Inner { format: Format, exclude: HashSet, exclude_regex: RegexSet, + log_target: Cow<'static, str>, } impl Logger { @@ -96,6 +98,7 @@ impl Logger { format: Format::new(format), exclude: HashSet::new(), exclude_regex: RegexSet::empty(), + log_target: Cow::Borrowed(module_path!()), })) } @@ -118,6 +121,24 @@ impl Logger { self } + /// Sets the logging target to `target`. + /// + /// By default, the log target is `module_path!()` of the log call location. In our case, that + /// would be `actix_web::middleware::logger`. + /// + /// # Examples + /// Using `.log_target("http_log")` would have this effect on request logs: + /// ```diff + /// - [2015-10-21T07:28:00Z INFO actix_web::middleware::logger] 127.0.0.1 "GET / HTTP/1.1" 200 88 "-" "dmc/1.0" 0.001985 + /// + [2015-10-21T07:28:00Z INFO http_log] 127.0.0.1 "GET / HTTP/1.1" 200 88 "-" "dmc/1.0" 0.001985 + /// ^^^^^^^^ + /// ``` + pub fn log_target(mut self, target: impl Into>) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + inner.log_target = target.into(); + self + } + /// Register a function that receives a ServiceRequest and returns a String for use in the /// log line. The label passed as the first argument should match a replacement substring in /// the logger format like `%{label}xi`. @@ -171,6 +192,7 @@ impl Default for Logger { format: Format::default(), exclude: HashSet::new(), exclude_regex: RegexSet::empty(), + log_target: Cow::Borrowed(module_path!()), })) } } @@ -222,13 +244,15 @@ where actix_service::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { - if self.inner.exclude.contains(req.path()) - || self.inner.exclude_regex.is_match(req.path()) - { + let excluded = self.inner.exclude.contains(req.path()) + || self.inner.exclude_regex.is_match(req.path()); + + if excluded { LoggerResponse { fut: self.service.call(req), format: None, time: OffsetDateTime::now_utc(), + log_target: Cow::Borrowed(""), _phantom: PhantomData, } } else { @@ -238,10 +262,12 @@ where for unit in &mut format.0 { unit.render_request(now, &req); } + LoggerResponse { fut: self.service.call(req), format: Some(format), time: now, + log_target: self.inner.log_target.clone(), _phantom: PhantomData, } } @@ -258,6 +284,7 @@ pin_project! { fut: S::Future, time: OffsetDateTime, format: Option, + log_target: Cow<'static, str>, _phantom: PhantomData, } } @@ -289,12 +316,14 @@ where let time = *this.time; let format = this.format.take(); + let log_target = this.log_target.clone(); Poll::Ready(Ok(res.map_body(move |_, body| StreamLog { body, time, format, size: 0, + log_target, }))) } } @@ -306,7 +335,9 @@ pin_project! { format: Option, size: usize, time: OffsetDateTime, + log_target: Cow<'static, str>, } + impl PinnedDrop for StreamLog { fn drop(this: Pin<&mut Self>) { if let Some(ref format) = this.format { @@ -316,7 +347,11 @@ pin_project! { } Ok(()) }; - log::info!("{}", FormatDisplay(&render)); + + log::info!( + target: this.log_target.as_ref(), + "{}", FormatDisplay(&render) + ); } } } From 9668a2396f3797a46c57c88baaa19e96cccc134f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 17:21:46 +0000 Subject: [PATCH 296/861] prepare actix-router release 0.5.0-rc.2 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce7eaeb61..2645ea2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = "3.0.0-beta.18" -actix-router = "0.5.0-rc.1" +actix-router = "0.5.0-rc.2" actix-web-codegen = "0.5.0-rc.1" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 17d149b69..6253b522a 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-rc.2 - 2022-01-21 - Add `Path::as_str`. [#2590] - Deprecate `Path::path`. [#2590] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 56a755ef4..0d4e4f897 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", From 141790b200e895d0c69365f8c7a1b39c486019cd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 20:15:43 +0000 Subject: [PATCH 297/861] use camel case in special headers fixes #2595 --- actix-http/src/config.rs | 34 +++++++++++++---- actix-http/src/h1/encoder.rs | 4 +- actix-http/src/helpers.rs | 65 +++++++++++++++++++++----------- actix-http/src/responses/head.rs | 12 +++++- 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 5d020edfc..b6d5a7d51 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -174,12 +174,15 @@ impl ServiceConfig { } #[doc(hidden)] - pub fn set_date(&self, dst: &mut BytesMut) { + pub fn set_date(&self, dst: &mut BytesMut, camel_case: bool) { let mut buf: [u8; 39] = [0; 39]; - buf[..6].copy_from_slice(b"date: "); + + buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); + self.0 .date_service .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); + buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } @@ -326,6 +329,7 @@ mod tests { use super::*; use actix_rt::{task::yield_now, time::sleep}; + use memchr::memmem; #[actix_rt::test] async fn test_date_service_update() { @@ -334,7 +338,7 @@ mod tests { yield_now().await; let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1); + settings.set_date(&mut buf1, false); let now1 = settings.now(); sleep_until(Instant::now() + Duration::from_secs(2)).await; @@ -342,7 +346,7 @@ mod tests { let now2 = settings.now(); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2); + settings.set_date(&mut buf2, false); assert_ne!(now1, now2); @@ -395,11 +399,27 @@ mod tests { #[actix_rt::test] async fn test_date() { - let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); + let settings = ServiceConfig::default(); + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1); + settings.set_date(&mut buf1, false); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2); + settings.set_date(&mut buf2, false); + assert_eq!(buf1, buf2); } + + #[actix_rt::test] + async fn test_date_camel_case() { + let settings = ServiceConfig::default(); + + let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf, false); + assert!(memmem::find(&buf, b"date:").is_some()); + + let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf, true); + assert!(memmem::find(&buf, b"Date:").is_some()); + } } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 8b1e3b623..bd0de75b9 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -105,7 +105,7 @@ pub(crate) trait MessageType: Sized { } BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"), BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"), - BodySize::Sized(len) => helpers::write_content_length(len, dst), + BodySize::Sized(len) => helpers::write_content_length(len, dst, camel_case), BodySize::None => dst.put_slice(b"\r\n"), } @@ -213,7 +213,7 @@ pub(crate) trait MessageType: Sized { // optimized date header, set_date writes \r\n if !has_date { - config.set_date(dst); + config.set_date(dst, camel_case); } else { // msg eof dst.extend_from_slice(b"\r\n"); diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index cba94d9b8..7f28018e7 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -30,15 +30,25 @@ pub(crate) fn write_status_line(version: Version, n: u16, buf: &mut B /// Write out content length header. /// /// Buffer must to contain enough space or be implicitly extendable. -pub fn write_content_length(n: u64, buf: &mut B) { +pub fn write_content_length(n: u64, buf: &mut B, camel_case: bool) { if n == 0 { - buf.put_slice(b"\r\ncontent-length: 0\r\n"); + if camel_case { + buf.put_slice(b"\r\nContent-Length: 0\r\n"); + } else { + buf.put_slice(b"\r\ncontent-length: 0\r\n"); + } + return; } let mut buffer = itoa::Buffer::new(); - buf.put_slice(b"\r\ncontent-length: "); + if camel_case { + buf.put_slice(b"\r\nContent-Length: "); + } else { + buf.put_slice(b"\r\ncontent-length: "); + } + buf.put_slice(buffer.format(n).as_bytes()); buf.put_slice(b"\r\n"); } @@ -95,77 +105,88 @@ mod tests { fn test_write_content_length() { let mut bytes = BytesMut::new(); bytes.reserve(50); - write_content_length(0, &mut bytes); + write_content_length(0, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]); bytes.reserve(50); - write_content_length(9, &mut bytes); + write_content_length(9, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]); bytes.reserve(50); - write_content_length(10, &mut bytes); + write_content_length(10, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]); bytes.reserve(50); - write_content_length(99, &mut bytes); + write_content_length(99, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]); bytes.reserve(50); - write_content_length(100, &mut bytes); + write_content_length(100, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]); bytes.reserve(50); - write_content_length(101, &mut bytes); + write_content_length(101, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]); bytes.reserve(50); - write_content_length(998, &mut bytes); + write_content_length(998, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]); bytes.reserve(50); - write_content_length(1000, &mut bytes); + write_content_length(1000, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); bytes.reserve(50); - write_content_length(1001, &mut bytes); + write_content_length(1001, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); bytes.reserve(50); - write_content_length(5909, &mut bytes); + write_content_length(5909, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); bytes.reserve(50); - write_content_length(9999, &mut bytes); + write_content_length(9999, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9999\r\n"[..]); bytes.reserve(50); - write_content_length(10001, &mut bytes); + write_content_length(10001, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10001\r\n"[..]); bytes.reserve(50); - write_content_length(59094, &mut bytes); + write_content_length(59094, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 59094\r\n"[..]); bytes.reserve(50); - write_content_length(99999, &mut bytes); + write_content_length(99999, &mut bytes, false); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99999\r\n"[..]); bytes.reserve(50); - write_content_length(590947, &mut bytes); + write_content_length(590947, &mut bytes, false); assert_eq!( bytes.split().freeze(), b"\r\ncontent-length: 590947\r\n"[..] ); bytes.reserve(50); - write_content_length(999999, &mut bytes); + write_content_length(999999, &mut bytes, false); assert_eq!( bytes.split().freeze(), b"\r\ncontent-length: 999999\r\n"[..] ); bytes.reserve(50); - write_content_length(5909471, &mut bytes); + write_content_length(5909471, &mut bytes, false); assert_eq!( bytes.split().freeze(), b"\r\ncontent-length: 5909471\r\n"[..] ); bytes.reserve(50); - write_content_length(59094718, &mut bytes); + write_content_length(59094718, &mut bytes, false); assert_eq!( bytes.split().freeze(), b"\r\ncontent-length: 59094718\r\n"[..] ); bytes.reserve(50); - write_content_length(4294973728, &mut bytes); + write_content_length(4294973728, &mut bytes, false); assert_eq!( bytes.split().freeze(), b"\r\ncontent-length: 4294973728\r\n"[..] ); } + + #[test] + fn write_content_length_camel_case() { + let mut bytes = BytesMut::new(); + write_content_length(0, &mut bytes, false); + assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]); + + let mut bytes = BytesMut::new(); + write_content_length(0, &mut bytes, true); + assert_eq!(bytes.split().freeze(), b"\r\nContent-Length: 0\r\n"[..]); + } } diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index 91e96a928..870073ab3 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -240,15 +240,23 @@ mod tests { let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert!(memmem::find(&data, b"Foo-Bar").is_some()); - assert!(!memmem::find(&data, b"foo-bar").is_some()); + assert!(memmem::find(&data, b"foo-bar").is_none()); + assert!(memmem::find(&data, b"Date").is_some()); + assert!(memmem::find(&data, b"date").is_none()); + assert!(memmem::find(&data, b"Content-Length").is_some()); + assert!(memmem::find(&data, b"content-length").is_none()); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - assert!(!memmem::find(&data, b"Foo-Bar").is_some()); + assert!(memmem::find(&data, b"Foo-Bar").is_none()); assert!(memmem::find(&data, b"foo-bar").is_some()); + assert!(memmem::find(&data, b"Date").is_none()); + assert!(memmem::find(&data, b"date").is_some()); + assert!(memmem::find(&data, b"Content-Length").is_none()); + assert!(memmem::find(&data, b"content-length").is_some()); srv.stop().await; } From 8865540f3b69db45cc4a37d579e39cfeddaf6953 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 20:21:40 +0000 Subject: [PATCH 298/861] prepare actix-http release 3.0.0-beta.19 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 7 +++++-- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2645ea2c4..115f0b3ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" actix-router = "0.5.0-rc.2" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 304cfa9da..013ddb552 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-beta.20", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 993dc854e..bdabea76c 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7fd635e3d..d4c5e9770 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.19 - 2022-01-21 ### Added - Response headers can be sent as camel case using `res.head_mut().set_camel_case_headers(true)`. [#2587] - `ResponseHead` now implements `Clone`. [#2585] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9fecd9a57..7ef5ca690 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "actix-http" -version = "3.0.0-beta.18" -authors = ["Nikolay Kim "] +version = "3.0.0-beta.19" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] description = "HTTP primitives for the Actix ecosystem" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" diff --git a/actix-http/README.md b/actix-http/README.md index 9883cc3f0..9f275f597 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.18)](https://docs.rs/actix-http/3.0.0-beta.18) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.19)](https://docs.rs/actix-http/3.0.0-beta.19) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.18) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.19) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 03a2bdfe2..d0d0c317e 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 9bd41ed0c..c15852220 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 169665ddf..7f0ae3d38 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" actix-web = { version = "4.0.0-beta.20", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0bce1b21f..85acbed5a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.18" +actix-http = "3.0.0-beta.19" actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.19", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } From c5d6df00786159f0c6478bf3dee7d7209f3a9f51 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 20:23:29 +0000 Subject: [PATCH 299/861] prepare actix-web release 4.0.0-beta.21 --- CHANGES.md | 3 +++ Cargo.toml | 7 +++++-- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1837aa577..d95bebaaa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.21 - 2022-01-21 ### Added - `HttpResponse::add_removal_cookie`. [#2586] - `Logger::log_target`. [#2594] diff --git a/Cargo.toml b/Cargo.toml index 115f0b3ac..f1ede7ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "actix-web" -version = "4.0.0-beta.20" -authors = ["Nikolay Kim "] +version = "4.0.0-beta.21" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" keywords = ["actix", "http", "web", "framework", "async"] categories = [ diff --git a/README.md b/README.md index 0085c1d6d..c3ea70f2c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.20)](https://docs.rs/actix-web/4.0.0-beta.20) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.21)](https://docs.rs/actix-web/4.0.0-beta.21) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.20/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.20) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.21/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.21)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 013ddb552..1db33b922 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-beta.19" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.20", default-features = false } +actix-web = { version = "4.0.0-beta.21", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.11" -actix-web = "4.0.0-beta.20" +actix-web = "4.0.0-beta.21" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bdabea76c..4fc169ac8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } actix-http = "3.0.0-beta.19" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7ef5ca690..e93d1b7af 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -85,7 +85,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.20" +actix-web = "4.0.0-beta.21" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index d0d0c317e..327252dbf 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.20", default-features = false } +actix-web = { version = "4.0.0-beta.21", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index c15852220..2d0d17d4e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7f0ae3d38..7c48b7ef0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-beta.19" -actix-web = { version = "4.0.0-beta.20", default-features = false } +actix-web = { version = "4.0.0-beta.21", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 51ccac27c..3ee2fbd02 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.11" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.20" +actix-web = "4.0.0-beta.21" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 85acbed5a..211f6ec89 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.20", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.21", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" From 6e9f5fba24bce012c2970e9e4ecee960ba86b33b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 20:25:46 +0000 Subject: [PATCH 300/861] prepare awc release 3.0.0-beta.19 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f1ede7ce0..713a4b6df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.14" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.18", features = ["openssl"] } +awc = { version = "3.0.0-beta.19", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 4fc169ac8..9bd3e9f9b 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2" -awc = { version = "3.0.0-beta.18", default-features = false } +awc = { version = "3.0.0-beta.19", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 2d0d17d4e..e2c75b01e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.19", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7c48b7ef0..3c28c0a15 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.11" -awc = { version = "3.0.0-beta.18", default-features = false } +awc = { version = "3.0.0-beta.19", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index f2c81ef25..e12dc8c27 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.19 - 2022-01-21 +- No significant changes since `3.0.0-beta.18`. + + ## 3.0.0-beta.18 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 211f6ec89..222765991 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.18" +version = "3.0.0-beta.19" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index 6a68ac05a..97e555d0d 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.18)](https://docs.rs/awc/3.0.0-beta.18) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.19)](https://docs.rs/awc/3.0.0-beta.19) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.18/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.18) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.19/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.19) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 232a14dc8bb2a1d777ddc633e51e50f89196b9c6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 20:27:29 +0000 Subject: [PATCH 301/861] prepare actix-files release 0.6.0-beta.15 --- Cargo.toml | 2 +- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 713a4b6df..99ff85e8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0-beta.14" +actix-files = "0.6.0-beta.15" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.19", features = ["openssl"] } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index f37e27518..fa9647e62 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.15 - 2022-01-21 +- No significant changes since `0.6.0-beta.14`. + + ## 0.6.0-beta.14 - 2022-01-14 - The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 1db33b922..68a3399a5 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.14" +version = "0.6.0-beta.15" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index 77dd1677e..8395957c5 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.14)](https://docs.rs/actix-files/0.6.0-beta.14) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.15)](https://docs.rs/actix-files/0.6.0-beta.15) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.14/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.14) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.15/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.15) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8459f566a866ee69b50a0b544f87541fba4cecf8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 21:17:07 +0000 Subject: [PATCH 302/861] fix brotli encoding buffer size --- actix-http/src/encoding/encoder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 9696da6f1..116fe76ab 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -391,9 +391,9 @@ impl ContentEncoder { fn new_brotli_compressor() -> Box> { Box::new(brotli::CompressorWriter::new( Writer::new(), - 8 * 1024, // 32 KiB buffer - 3, // BROTLI_PARAM_QUALITY - 22, // BROTLI_PARAM_LGWIN + 32 * 1024, // 32 KiB buffer + 3, // BROTLI_PARAM_QUALITY + 22, // BROTLI_PARAM_LGWIN )) } From acacb90b2e948ce3c47c918c3d3c0a747573f18b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 21 Jan 2022 21:24:09 +0000 Subject: [PATCH 303/861] add actix-http 2.2.2 changelog --- actix-http/CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d4c5e9770..6047a6bc5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -416,6 +416,13 @@ [#1878]: https://github.com/actix/actix-web/pull/1878 +## 2.2.2 - 2022-01-21 +### Changed +- Migrate to `brotli` crate. [ad7e3c06] + +[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06 + + ## 2.2.1 - 2021-08-09 ### Fixed - Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) From c25dd238200bb314eb4d21c102ce79a7655fa3ad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 22 Jan 2022 04:02:34 +0000 Subject: [PATCH 304/861] move path impls to derives --- actix-files/src/files.rs | 10 +++++----- actix-files/src/service.rs | 2 +- src/types/path.rs | 39 +++++--------------------------------- 3 files changed, 11 insertions(+), 40 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index adfb93232..a30ce6fd3 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -37,7 +37,7 @@ use crate::{ /// .service(Files::new("/static", ".")); /// ``` pub struct Files { - path: String, + mount_path: String, directory: PathBuf, index: Option, show_index: bool, @@ -68,7 +68,7 @@ impl Clone for Files { default: self.default.clone(), renderer: self.renderer.clone(), file_flags: self.file_flags, - path: self.path.clone(), + mount_path: self.mount_path.clone(), mime_override: self.mime_override.clone(), path_filter: self.path_filter.clone(), use_guards: self.use_guards.clone(), @@ -107,7 +107,7 @@ impl Files { }; Files { - path: mount_path.trim_end_matches('/').to_owned(), + mount_path: mount_path.trim_end_matches('/').to_owned(), directory: dir, index: None, show_index: false, @@ -342,9 +342,9 @@ impl HttpServiceFactory for Files { } let rdef = if config.is_root() { - ResourceDef::root_prefix(&self.path) + ResourceDef::root_prefix(&self.mount_path) } else { - ResourceDef::prefix(&self.path) + ResourceDef::prefix(&self.mount_path) }; config.register_service(rdef, guards, self, None) diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 5d494f878..ec09af01c 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -168,7 +168,7 @@ impl Service for FilesService { } } None if this.show_index => Ok(this.show_index(req, path)), - _ => Ok(ServiceResponse::from_err( + None => Ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, )), diff --git a/src/types/path.rs b/src/types/path.rs index c3efc22c0..58a1a5bde 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -1,9 +1,10 @@ //! For path segment extractor documentation, see [`Path`]. -use std::{fmt, ops, sync::Arc}; +use std::sync::Arc; use actix_router::PathDeserializer; use actix_utils::future::{ready, Ready}; +use derive_more::{AsRef, Deref, DerefMut, Display, From}; use serde::de; use crate::{ @@ -49,7 +50,9 @@ use crate::{ /// format!("Welcome {}!", info.name) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From, +)] pub struct Path(T); impl Path { @@ -59,38 +62,6 @@ impl Path { } } -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl ops::Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl ops::DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path(inner) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - /// See [here](#Examples) for example of usage as an extractor. impl FromRequest for Path where From c92aa31f9195372053865ecc0b1fcd058744405f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 22 Jan 2022 16:17:46 +0000 Subject: [PATCH 305/861] document full percent-decoding of web::Path --- src/request.rs | 13 +++++++------ src/types/path.rs | 8 ++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/request.rs b/src/request.rs index b3db5ffbd..f04e551d4 100644 --- a/src/request.rs +++ b/src/request.rs @@ -129,10 +129,11 @@ impl HttpRequest { /// later in a request handler to access the matched value for that parameter. /// /// # Percent Encoding and URL Parameters - /// Because each URL parameter is able to capture multiple path segments, both `["%2F", "%25"]` - /// found in the request URI are not decoded into `["/", "%"]` in order to preserve path - /// segment boundaries. If a url parameter is expected to contain these characters, then it is - /// on the user to decode them. + /// Because each URL parameter is able to capture multiple path segments, none of + /// `["%2F", "%25", "%2B"]` found in the request URI are decoded into `["/", "%", "+"]` in order + /// to preserve path integrity. If a URL parameter is expected to contain these characters, then + /// it is on the user to decode them or use the [`web::Path`](crate::web::Path) extractor which + /// _will_ decode these special sequences. #[inline] pub fn match_info(&self) -> &Path { &self.inner.path @@ -504,12 +505,11 @@ impl HttpRequestPool { #[cfg(test)] mod tests { - use actix_service::Service; use bytes::Bytes; use super::*; use crate::{ - dev::{ResourceDef, ResourceMap}, + dev::{ResourceDef, ResourceMap, Service}, http::{header, StatusCode}, test::{self, call_service, init_service, read_body, TestRequest}, web, App, HttpResponse, @@ -902,6 +902,7 @@ mod tests { // `body` equals http://localhost:8080/bar/nested // because nested from /bar overrides /foo's // to do this any other way would require something like a custom tree search + // see https://github.com/actix/actix-web/issues/1763 assert_eq!(body, "http://localhost:8080/bar/nested"); let bar_resp = diff --git a/src/types/path.rs b/src/types/path.rs index 58a1a5bde..869269d09 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -18,6 +18,9 @@ use crate::{ /// /// Use [`PathConfig`] to configure extraction option. /// +/// Unlike, [`HttpRequest::match_info`], this extractor will fully percent-decode dynamic segments, +/// including `/`, `%`, and `+`. +/// /// # Examples /// ``` /// use actix_web::{get, web}; @@ -259,13 +262,14 @@ mod tests { #[actix_rt::test] async fn paths_decoded() { let resource = ResourceDef::new("/{key}/{value}"); - let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%251").to_srv_request(); + let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request(); resource.capture_match_info(req.match_info_mut()); let (req, mut pl) = req.into_parts(); let path_items = Path::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(path_items.key, "na+me"); - assert_eq!(path_items.value, "us/er%1"); + assert_eq!(path_items.value, "us/er%42"); + assert_eq!(req.match_info().as_str(), "/na%2Bme/us%2Fer%2542"); } #[actix_rt::test] From 008753f07a30bd5dbc875545f5012075ad88ae0d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 23 Jan 2022 03:57:08 +0000 Subject: [PATCH 306/861] improve body docs --- actix-http/src/body/boxed.rs | 2 +- actix-http/src/body/message_body.rs | 65 ++++++++++++++++++++++++++--- actix-http/src/body/mod.rs | 5 +++ actix-http/src/body/none.rs | 7 +++- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index d109a6a74..cac6b7eb9 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -31,7 +31,7 @@ impl fmt::Debug for BoxBodyInner { } impl BoxBody { - /// Same as `MessageBody::boxed`. + /// Boxes body type, erasing type information. /// /// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to /// avoid double boxing. diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 0a605a69a..86ff09fbe 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -14,8 +14,44 @@ use pin_project_lite::pin_project; use super::{BodySize, BoxBody}; -/// An interface types that can converted to bytes and used as response bodies. -// TODO: examples +/// An interface for types that can be used as a response body. +/// +/// It is not usually necessary to create custom body types, this trait is already [implemented for +/// a large number of sensible body types](#foreign-impls) including: +/// - Empty body: `()` +/// - Text-based: `String`, `&'static str`, `ByteString`. +/// - Byte-based: `Bytes`, `BytesMut`, `Vec`, `&'static [u8]`; +/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream) +/// +/// # Examples +/// ``` +/// # use std::convert::Infallible; +/// # use std::task::{Poll, Context}; +/// # use std::pin::Pin; +/// # use bytes::Bytes; +/// # use actix_http::body::{BodySize, MessageBody}; +/// struct Repeat { +/// chunk: String, +/// n_times: usize, +/// } +/// +/// impl MessageBody for Repeat { +/// type Error = Infallible; +/// +/// fn size(&self) -> BodySize { +/// BodySize::Sized((self.chunk.len() * self.n_times) as u64) +/// } +/// +/// fn poll_next( +/// self: Pin<&mut Self>, +/// _cx: &mut Context<'_>, +/// ) -> Poll>> { +/// let payload_string = self.chunk.repeat(self.n_times); +/// let payload_bytes = Bytes::from(payload_string); +/// Poll::Ready(Some(Ok(payload_bytes))) +/// } +/// } +/// ``` pub trait MessageBody { /// The type of error that will be returned if streaming body fails. /// @@ -29,7 +65,22 @@ pub trait MessageBody { fn size(&self) -> BodySize; /// Attempt to pull out the next chunk of body bytes. - // TODO: expand documentation + /// + /// # Return Value + /// Similar to the `Stream` interface, there are several possible return values, each indicating + /// a distinct state: + /// - `Poll::Pending` means that this body's next chunk is not ready yet. Implementations must + /// ensure that the current task will be notified when the next chunk may be ready. + /// - `Poll::Ready(Some(val))` means that the body has successfully produced a chunk, `val`, + /// and may produce further values on subsequent `poll_next` calls. + /// - `Poll::Ready(None)` means that the body is complete, and `poll_next` should not be + /// invoked again. + /// + /// # Panics + /// Once a body is complete (i.e., `poll_next` returned `Ready(None)`), calling its `poll_next` + /// method again may panic, block forever, or cause other kinds of problems; this trait places + /// no requirements on the effects of such a call. However, as the `poll_next` method is not + /// marked unsafe, Rust’s usual rules apply: calls must never cause UB, regardless of its state. fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -37,7 +88,7 @@ pub trait MessageBody { /// Try to convert into the complete chunk of body bytes. /// - /// Implement this method if the entire body can be trivially extracted. This is useful for + /// Override this method if the complete body can be trivially extracted. This is useful for /// optimizations where `poll_next` calls can be avoided. /// /// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling @@ -54,7 +105,11 @@ pub trait MessageBody { Err(self) } - /// Converts this body into `BoxBody`. + /// Wraps this body into a `BoxBody`. + /// + /// No-op when called on a `BoxBody`, meaning there is no risk of double boxing when calling + /// this on a generic `MessageBody`. Prefer this over [`BoxBody::new`] when a boxed body + /// is required. #[inline] fn boxed(self) -> BoxBody where diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index af7c4626f..0fb090eb5 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,4 +1,9 @@ //! Traits and structures to aid consuming and writing HTTP payloads. +//! +//! "Body" and "payload" are used somewhat interchangeably in this documentation. + +// Though the spec kinda reads like "payload" is the possibly-transfer-encoded part of the message +// and the "body" is the intended possibly-decoded version of that. mod body_stream; mod boxed; diff --git a/actix-http/src/body/none.rs b/actix-http/src/body/none.rs index 0e7bbe5a9..b1d3f7f2a 100644 --- a/actix-http/src/body/none.rs +++ b/actix-http/src/body/none.rs @@ -10,9 +10,12 @@ use super::{BodySize, MessageBody}; /// Body type for responses that forbid payloads. /// -/// Distinct from an empty response which would contain a Content-Length header. -/// +/// This is distinct from an "empty" response which _would_ contain a `Content-Length` header. /// For an "empty" body, use `()` or `Bytes::new()`. +/// +/// For example, the HTTP spec forbids a payload to be sent with a `204 No Content` response. +/// In this case, the payload (or lack thereof) is implicit from the status code, so a +/// `Content-Length` header is not required. #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub struct None; From 50894e392ea5b6ad83bfc723139ff5a524913127 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 23 Jan 2022 23:26:35 +0000 Subject: [PATCH 307/861] document new body map types --- actix-http/src/h1/encoder.rs | 1 - actix-http/src/payload.rs | 3 ++- actix-http/src/responses/response.rs | 5 +++-- awc/src/client/mod.rs | 5 ++--- awc/src/middleware/redirect.rs | 13 +++++++------ src/app_service.rs | 1 - src/error/error.rs | 14 ++++++-------- src/response/response.rs | 13 +++++++++---- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bd0de75b9..5fcb2f688 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -152,7 +152,6 @@ pub(crate) trait MessageType: Sized { let k = key.as_str().as_bytes(); let k_len = k.len(); - // TODO: drain? for val in value.iter() { let v = val.as_ref(); let v_len = v.len(); diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index c9f338c7d..aed24e963 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -6,6 +6,7 @@ use std::{ use bytes::Bytes; use futures_core::Stream; +use pin_project_lite::pin_project; use crate::error::PayloadError; @@ -15,7 +16,7 @@ pub type BoxedPayloadStream = Pin { diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index 6efc3c5f1..da5503c1c 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -185,7 +185,7 @@ impl Response { self.replace_body(()) } - /// Map the current body type to another using a closure. Returns a new response. + /// Map the current body type to another using a closure, returning a new response. /// /// Closure receives the response head and the current body type. #[inline] @@ -202,6 +202,7 @@ impl Response { } } + /// Map the current body to a type-erased `BoxBody`. #[inline] pub fn map_into_boxed_body(self) -> Response where @@ -210,7 +211,7 @@ impl Response { self.map_body(|_, body| body.boxed()) } - /// Returns body, consuming this response. + /// Returns the response body, dropping all other parts. #[inline] pub fn into_body(self) -> B { self.body diff --git a/awc/src/client/mod.rs b/awc/src/client/mod.rs index 443bf1239..e898d2d04 100644 --- a/awc/src/client/mod.rs +++ b/awc/src/client/mod.rs @@ -94,10 +94,9 @@ impl Client { let mut req = ClientRequest::new(method, url, self.0.clone()); for header in self.0.default_headers.iter() { - // header map is empty - // TODO: probably append instead - req = req.insert_header_if_none(header); + req = req.append_header(header); } + req } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 704d2d79d..ac6690471 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -163,7 +163,7 @@ where | StatusCode::PERMANENT_REDIRECT if *max_redirect_times > 0 => { - let is_redirect = res.head().status == StatusCode::TEMPORARY_REDIRECT + let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT || res.head().status == StatusCode::PERMANENT_REDIRECT; let prev_uri = uri.take().unwrap(); @@ -176,7 +176,7 @@ where let connector = connector.take(); // reset method - let method = if is_redirect { + let method = if reuse_body { method.take().unwrap() } else { let method = method.take().unwrap(); @@ -187,18 +187,19 @@ where }; let mut body = body.take(); - let body_new = if is_redirect { - // try to reuse body + let body_new = if reuse_body { + // try to reuse saved body match body { Some(ref bytes) => AnyBody::Bytes { body: bytes.clone(), }, - // TODO: should this be AnyBody::Empty or AnyBody::None. + + // body was a non-reusable type so send an empty body instead _ => AnyBody::empty(), } } else { body = None; - // remove body + // remove body since we're downgrading to a GET AnyBody::None }; diff --git a/src/app_service.rs b/src/app_service.rs index b7c016e81..edfb3e4a2 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -169,7 +169,6 @@ impl AppInitServiceState { Rc::new(AppInitServiceState { rmap, config, - // TODO: AppConfig can be used to pass user defined HttpRequestPool capacity. pool: HttpRequestPool::default(), }) } diff --git a/src/error/error.rs b/src/error/error.rs index be17c1962..8450bed35 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -4,16 +4,14 @@ use actix_http::{body::BoxBody, Response}; use crate::{HttpResponse, ResponseError}; -/// General purpose actix web error. +/// General purpose Actix Web error. /// -/// An actix web error is used to carry errors from `std::error` -/// through actix in a convenient way. It can be created through -/// converting errors with `into()`. +/// An Actix Web error is used to carry errors from `std::error` through actix in a convenient way. +/// It can be created through converting errors with `into()`. /// -/// Whenever it is created from an external object a response error is created -/// for it that can be used to create an HTTP response from it this means that -/// if you have access to an actix `Error` you can always get a -/// `ResponseError` reference from it. +/// Whenever it is created from an external object a response error is created for it that can be +/// used to create an HTTP response from it this means that if you have access to an actix `Error` +/// you can always get a `ResponseError` reference from it. pub struct Error { cause: Box, } diff --git a/src/response/response.rs b/src/response/response.rs index 33f0a54a6..6a326fa61 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -256,7 +256,7 @@ impl HttpResponse { } } - /// Map the current body type to another using a closure. Returns a new response. + /// Map the current body type to another using a closure, returning a new response. /// /// Closure receives the response head and the current body type. pub fn map_body(self, f: F) -> HttpResponse @@ -269,18 +269,23 @@ impl HttpResponse { } } - // TODO: docs for the body map methods below - + /// Map the current body type `B` to `EitherBody::Left(B)`. + /// + /// Useful for middleware which can generate their own responses. #[inline] pub fn map_into_left_body(self) -> HttpResponse> { self.map_body(|_, body| EitherBody::left(body)) } + /// Map the current body type `B` to `EitherBody::Right(B)`. + /// + /// Useful for middleware which can generate their own responses. #[inline] pub fn map_into_right_body(self) -> HttpResponse> { self.map_body(|_, body| EitherBody::right(body)) } + /// Map the current body to a type-erased `BoxBody`. #[inline] pub fn map_into_boxed_body(self) -> HttpResponse where @@ -289,7 +294,7 @@ impl HttpResponse { self.map_body(|_, body| body.boxed()) } - /// Extract response body + /// Returns the response body, dropping all other parts. pub fn into_body(self) -> B { self.res.into_body() } From d7c5c966d2271add3babdbb63cbe9aa423c87909 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 24 Jan 2022 11:56:01 +0000 Subject: [PATCH 308/861] remove impl Future for HttpResponse (#2601) --- CHANGES.md | 4 ++ actix-files/src/lib.rs | 32 ++++----- actix-web-actors/src/context.rs | 9 ++- awc/tests/test_client.rs | 53 ++++++++------ benches/server.rs | 5 +- src/guard.rs | 12 +++- src/middleware/compress.rs | 2 +- src/resource.rs | 8 +-- src/response/response.rs | 51 ++++++++------ src/types/payload.rs | 6 +- tests/compression.rs | 15 ++-- tests/test_httpserver.rs | 5 +- tests/test_server.rs | 119 +++++++++++++++++--------------- 13 files changed, 182 insertions(+), 139 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d95bebaaa..8c3997663 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +- `impl Future for HttpResponse`. [#2601] + +[#2601]: https://github.com/actix/actix-web/pull/2601 ## 4.0.0-beta.21 - 2022-01-21 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 43b06a858..41113f2ab 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -106,7 +106,7 @@ mod tests { let req = TestRequest::default() .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); } @@ -118,7 +118,7 @@ mod tests { let req = TestRequest::default() .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); } @@ -131,7 +131,7 @@ mod tests { .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(); + let resp = file.respond_to(&req); assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); } @@ -143,7 +143,7 @@ mod tests { let req = TestRequest::default() .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); } @@ -155,7 +155,7 @@ mod tests { let req = TestRequest::default() .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED); } @@ -172,7 +172,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -196,7 +196,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=\"Cargo.toml\"" @@ -207,7 +207,7 @@ mod tests { .unwrap() .disable_content_disposition(); let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); } @@ -235,7 +235,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -261,7 +261,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/xml" @@ -284,7 +284,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png" @@ -300,7 +300,7 @@ mod tests { let file = NamedFile::open_async("tests/test.js").await.unwrap(); let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/javascript; charset=utf-8" @@ -330,7 +330,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png" @@ -353,7 +353,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/octet-stream" @@ -379,7 +379,7 @@ mod tests { } let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -633,7 +633,7 @@ mod tests { async fn test_named_file_allowed_method() { let req = TestRequest::default().method(Method::GET).to_http_request(); let file = NamedFile::open_async("Cargo.toml").await.unwrap(); - let resp = file.respond_to(&req).await.unwrap(); + let resp = file.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index d7459aea4..d83969ff7 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -228,11 +228,10 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let 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(|| async { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c1378157b..dceaf467d 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -30,7 +30,9 @@ const STR: &str = const_str::repeat!(S, 100); #[actix_rt::test] async fn test_simple() { let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service( + web::resource("/").route(web::to(|| async { HttpResponse::Ok().body(STR) })), + ) }); let request = srv.get("/").insert_header(("x-test", "111")).send(); @@ -93,7 +95,7 @@ async fn test_timeout() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) + HttpResponse::Ok().body(STR) }))) }); @@ -118,7 +120,7 @@ async fn test_timeout_override() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) + HttpResponse::Ok().body(STR) }))) }); @@ -295,10 +297,9 @@ async fn test_connection_server_close() { }) .and_then( HttpService::new(map_config( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().finish())), - ), + App::new().service(web::resource("/").route(web::to(|| async { + HttpResponse::Ok().force_close().finish() + }))), |_| AppConfig::default(), )) .tcp(), @@ -336,7 +337,8 @@ async fn test_connection_wait_queue() { .and_then( HttpService::new(map_config( App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + web::resource("/") + .route(web::to(|| async { HttpResponse::Ok().body(STR) })), ), |_| AppConfig::default(), )) @@ -383,10 +385,9 @@ async fn test_connection_wait_queue_force_close() { }) .and_then( HttpService::new(map_config( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), - ), + App::new().service(web::resource("/").route(web::to(|| async { + HttpResponse::Ok().force_close().body(STR) + }))), |_| AppConfig::default(), )) .tcp(), @@ -445,7 +446,9 @@ async fn test_no_decompress() { let srv = actix_test::start(|| { App::new() .wrap(actix_web::middleware::Compress::default()) - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + .service( + web::resource("/").route(web::to(|| async { HttpResponse::Ok().body(STR) })), + ) }); let mut res = awc::Client::new() @@ -479,7 +482,7 @@ async fn test_no_decompress() { #[actix_rt::test] async fn test_client_gzip_encoding() { let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { + App::new().service(web::resource("/").route(web::to(|| async { HttpResponse::Ok() .insert_header(header::ContentEncoding::Gzip) .body(utils::gzip::encode(STR)) @@ -499,7 +502,7 @@ async fn test_client_gzip_encoding() { #[actix_rt::test] async fn test_client_gzip_encoding_large() { let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { + App::new().service(web::resource("/").route(web::to(|| async { HttpResponse::Ok() .insert_header(header::ContentEncoding::Gzip) .body(utils::gzip::encode(STR.repeat(10))) @@ -525,7 +528,7 @@ async fn test_client_gzip_encoding_large_random() { .collect::(); let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { + App::new().service(web::resource("/").route(web::to(|data: Bytes| async { HttpResponse::Ok() .insert_header(header::ContentEncoding::Gzip) .body(utils::gzip::encode(data)) @@ -545,7 +548,7 @@ async fn test_client_gzip_encoding_large_random() { #[actix_rt::test] async fn test_client_brotli_encoding() { let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { + App::new().service(web::resource("/").route(web::to(|data: Bytes| async { HttpResponse::Ok() .insert_header(("content-encoding", "br")) .body(utils::brotli::encode(data)) @@ -571,10 +574,10 @@ async fn test_client_brotli_encoding_large_random() { .collect::(); let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { + App::new().service(web::resource("/").route(web::to(|data: Bytes| async { HttpResponse::Ok() .insert_header(header::ContentEncoding::Brotli) - .body(utils::brotli::encode(&data)) + .body(utils::brotli::encode(data)) }))) }); @@ -590,7 +593,9 @@ async fn test_client_brotli_encoding_large_random() { #[actix_rt::test] async fn test_client_deflate_encoding() { let srv = actix_test::start(|| { - App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body))) + App::new().default_service(web::to(|body: Bytes| async { + HttpResponse::Ok().body(body) + })) }); let req = srv @@ -614,7 +619,9 @@ async fn test_client_deflate_encoding_large_random() { .collect::(); let srv = actix_test::start(|| { - App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body))) + App::new().default_service(web::to(|body: Bytes| async { + HttpResponse::Ok().body(body) + })) }); let req = srv @@ -632,7 +639,7 @@ async fn test_client_deflate_encoding_large_random() { #[actix_rt::test] async fn test_client_streaming_explicit() { let srv = actix_test::start(|| { - App::new().default_service(web::to(|body: web::Payload| { + App::new().default_service(web::to(|body: web::Payload| async { HttpResponse::Ok().streaming(body) })) }); @@ -654,7 +661,7 @@ async fn test_client_streaming_explicit() { #[actix_rt::test] async fn test_body_streaming_implicit() { let srv = actix_test::start(|| { - App::new().default_service(web::to(|| { + App::new().default_service(web::to(|| async { let body = stream::once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_bytes())) }); HttpResponse::Ok().streaming(body) diff --git a/benches/server.rs b/benches/server.rs index 139e24abd..0d45c9403 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -33,8 +33,9 @@ fn bench_async_burst(c: &mut Criterion) { let srv = rt.block_on(async { actix_test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service( + web::resource("/").route(web::to(|| async { HttpResponse::Ok().body(STR) })), + ) }) }); diff --git a/src/guard.rs b/src/guard.rs index 596b9f9fe..9f7514644 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -373,7 +373,9 @@ impl Guard for HeaderGuard { /// /// web::scope("/admin") /// .guard(Host("admin.rust-lang.org").scheme("https")) -/// .default_service(web::to(|| HttpResponse::Ok().body("admin connection is secure"))); +/// .default_service(web::to(|| async { +/// HttpResponse::Ok().body("admin connection is secure") +/// })); /// ``` /// /// The `Host` guard can be used to set up some form of [virtual hosting] within a single app. @@ -388,12 +390,16 @@ impl Guard for HeaderGuard { /// .service( /// web::scope("") /// .guard(guard::Host("www.rust-lang.org")) -/// .default_service(web::to(|| HttpResponse::Ok().body("marketing site"))), +/// .default_service(web::to(|| async { +/// HttpResponse::Ok().body("marketing site") +/// })), /// ) /// .service( /// web::scope("") /// .guard(guard::Host("play.rust-lang.org")) -/// .default_service(web::to(|| HttpResponse::Ok().body("playground frontend"))), +/// .default_service(web::to(|| async { +/// HttpResponse::Ok().body("playground frontend") +/// })), /// ); /// ``` /// diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 16af4c2cd..4fdd74779 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -52,7 +52,7 @@ use crate::{ /// /// let app = App::new() /// .wrap(middleware::Compress::default()) -/// .default_service(web::to(|| HttpResponse::Ok().body("hello world"))); +/// .default_service(web::to(|| async { HttpResponse::Ok().body("hello world") })); /// ``` /// /// Pre-compressed Gzip file being served from disk with correct headers added to bypass middleware: diff --git a/src/resource.rs b/src/resource.rs index dd7d4b0d5..a0fc19faf 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -206,10 +206,10 @@ where /// Register a new route and add handler. This route matches all requests. /// /// ``` - /// use actix_web::*; + /// use actix_web::{App, HttpRequest, HttpResponse, web}; /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// unimplemented!() + /// async fn index(req: HttpRequest) -> HttpResponse { + /// todo!() /// } /// /// App::new().service(web::resource("/").to(index)); @@ -219,7 +219,7 @@ where /// /// ``` /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// # async fn index(req: HttpRequest) -> HttpResponse { todo!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` pub fn to(mut self, handler: F) -> Self diff --git a/src/response/response.rs b/src/response/response.rs index 6a326fa61..0469c5ce9 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -1,10 +1,6 @@ use std::{ cell::{Ref, RefMut}, fmt, - future::Future, - mem, - pin::Pin, - task::{Context, Poll}, }; use actix_http::{ @@ -337,24 +333,39 @@ impl From> for Response { } } -// Future is only implemented for BoxBody payload type because it's the most useful for making -// simple handlers without async blocks. Making it generic over all MessageBody types requires a -// future impl on Response which would cause it's body field to be, undesirably, Option. -// -// This impl is not particularly efficient due to the Response construction and should probably -// not be invoked if performance is important. Prefer an async fn/block in such cases. -impl Future for HttpResponse { - type Output = Result, Error>; +// Rationale for cfg(test): this impl causes false positives on a clippy lint (async_yields_async) +// when returning an HttpResponse from an async function/closure and it's not very useful outside of +// tests anyway. +#[cfg(test)] +mod response_fut_impl { + use std::{ + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, + }; - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - if let Some(err) = self.error.take() { - return Poll::Ready(Err(err)); + use super::*; + + // Future is only implemented for BoxBody payload type because it's the most useful for making + // simple handlers without async blocks. Making it generic over all MessageBody types requires a + // future impl on Response which would cause it's body field to be, undesirably, Option. + // + // This impl is not particularly efficient due to the Response construction and should probably + // not be invoked if performance is important. Prefer an async fn/block in such cases. + impl Future for HttpResponse { + type Output = Result, Error>; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + if let Some(err) = self.error.take() { + return Poll::Ready(Err(err)); + } + + Poll::Ready(Ok(mem::replace( + &mut self.res, + Response::new(StatusCode::default()), + ))) } - - Poll::Ready(Ok(mem::replace( - &mut self.res, - Response::new(StatusCode::default()), - ))) } } diff --git a/src/types/payload.rs b/src/types/payload.rs index d2ab29639..b47a39e97 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -219,7 +219,7 @@ impl PayloadConfig { } } - /// Set maximum accepted payload size in bytes. The default limit is 256kB. + /// Set maximum accepted payload size in bytes. The default limit is 256KiB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -261,14 +261,14 @@ impl PayloadConfig { } } +const DEFAULT_CONFIG_LIMIT: usize = 262_144; // 2^18 bytes (~256kB) + /// Allow shared refs used as defaults. const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { limit: DEFAULT_CONFIG_LIMIT, mimetype: None, }; -const DEFAULT_CONFIG_LIMIT: usize = 262_144; // 2^18 bytes (~256kB) - impl Default for PayloadConfig { fn default() -> Self { DEFAULT_CONFIG.clone() diff --git a/tests/compression.rs b/tests/compression.rs index 88c462f60..b911b9d1f 100644 --- a/tests/compression.rs +++ b/tests/compression.rs @@ -19,10 +19,13 @@ macro_rules! test_server { actix_test::start(|| { App::new() .wrap(Compress::default()) - .route("/static", web::to(|| HttpResponse::Ok().body(LOREM))) + .route( + "/static", + web::to(|| async { HttpResponse::Ok().body(LOREM) }), + ) .route( "/static-gzip", - web::to(|| { + web::to(|| async { HttpResponse::Ok() // signal to compressor that content should not be altered // signal to client that content is encoded @@ -32,7 +35,7 @@ macro_rules! test_server { ) .route( "/static-br", - web::to(|| { + web::to(|| async { HttpResponse::Ok() // signal to compressor that content should not be altered // signal to client that content is encoded @@ -42,7 +45,7 @@ macro_rules! test_server { ) .route( "/static-zstd", - web::to(|| { + web::to(|| async { HttpResponse::Ok() // signal to compressor that content should not be altered // signal to client that content is encoded @@ -52,7 +55,7 @@ macro_rules! test_server { ) .route( "/static-xz", - web::to(|| { + web::to(|| async { HttpResponse::Ok() // signal to compressor that content should not be altered // signal to client that content is encoded as 7zip @@ -62,7 +65,7 @@ macro_rules! test_server { ) .route( "/echo", - web::to(|body: Bytes| HttpResponse::Ok().body(body)), + web::to(|body: Bytes| async move { HttpResponse::Ok().body(body) }), ) }) }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 464a650a2..6ea8e520c 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -18,7 +18,8 @@ async fn test_start() { .block_on(async { let srv = HttpServer::new(|| { App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), + web::resource("/") + .route(web::to(|| async { HttpResponse::Ok().body("test") })), ) }) .workers(1) @@ -93,7 +94,7 @@ async fn test_start_ssl() { let srv = HttpServer::new(|| { App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { assert!(req.app_config().secure()); - HttpResponse::Ok().body("test") + async { HttpResponse::Ok().body("test") } }))) }) .workers(1) diff --git a/tests/test_server.rs b/tests/test_server.rs index ade48a485..b8193a004 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -93,7 +93,9 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { let srv = actix_test::start(|| { - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service( + web::resource("/").route(web::to(|| async { HttpResponse::Ok().body(STR) })), + ) }); let mut res = srv.get("/").send().await.unwrap(); @@ -160,9 +162,12 @@ async fn body_gzip_large() { let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); - App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(data.clone()))), - ) + App::new() + .wrap(Compress::default()) + .service(web::resource("/").route(web::to(move || { + let data = data.clone(); + async move { HttpResponse::Ok().body(data.clone()) } + }))) }); let mut res = srv @@ -191,9 +196,12 @@ async fn test_body_gzip_large_random() { let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); - App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(data.clone()))), - ) + App::new() + .wrap(Compress::default()) + .service(web::resource("/").route(web::to(move || { + let data = data.clone(); + async move { HttpResponse::Ok().body(data.clone()) } + }))) }); let mut res = srv @@ -216,7 +224,7 @@ async fn test_body_chunked_implicit() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::default()) - .service(web::resource("/").route(web::get().to(move || { + .service(web::resource("/").route(web::get().to(|| async { HttpResponse::Ok() .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) }))) @@ -246,7 +254,7 @@ async fn test_body_br_streaming() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || { + .service(web::resource("/").route(web::to(|| async { HttpResponse::Ok() .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) }))) @@ -271,7 +279,8 @@ async fn test_body_br_streaming() { async fn test_head_binary() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( - web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), + web::resource("/") + .route(web::head().to(move || async { HttpResponse::Ok().body(STR) })), ) }); @@ -290,7 +299,7 @@ async fn test_head_binary() { #[actix_rt::test] async fn test_no_chunking() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service(web::resource("/").route(web::to(move || { + App::new().service(web::resource("/").route(web::to(move || async { HttpResponse::Ok() .no_chunking(STR.len() as u64) .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) @@ -310,9 +319,9 @@ async fn test_no_chunking() { #[actix_rt::test] async fn test_body_deflate() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new() - .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) + App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(move || async { HttpResponse::Ok().body(STR) })), + ) }); let mut res = srv @@ -333,9 +342,9 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new() - .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) + App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(move || async { HttpResponse::Ok().body(STR) })), + ) }); let mut res = srv @@ -356,9 +365,9 @@ async fn test_body_brotli() { #[actix_rt::test] async fn test_body_zstd() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new() - .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) + App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(move || async { HttpResponse::Ok().body(STR) })), + ) }); let mut res = srv @@ -381,7 +390,7 @@ async fn test_body_zstd_streaming() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || { + .service(web::resource("/").route(web::to(move || async { HttpResponse::Ok() .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) }))) @@ -405,9 +414,9 @@ async fn test_body_zstd_streaming() { #[actix_rt::test] async fn test_zstd_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -435,7 +444,7 @@ async fn test_zstd_encoding_large() { App::new().service( web::resource("/") .app_data(web::PayloadConfig::new(320_000)) - .route(web::to(move |body: Bytes| { + .route(web::to(move |body: Bytes| async { HttpResponse::Ok().streaming(TestBody::new(body, 10240)) })), ) @@ -457,9 +466,11 @@ async fn test_zstd_encoding_large() { #[actix_rt::test] async fn test_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new() + .wrap(Compress::default()) + .service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -478,9 +489,9 @@ async fn test_encoding() { #[actix_rt::test] async fn test_gzip_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -500,9 +511,9 @@ async fn test_gzip_encoding() { async fn test_gzip_encoding_large() { let data = STR.repeat(10); let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let req = srv @@ -527,9 +538,9 @@ async fn test_reading_gzip_encoding_large_random() { .collect::(); let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -548,9 +559,9 @@ async fn test_reading_gzip_encoding_large_random() { #[actix_rt::test] async fn test_reading_deflate_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -570,9 +581,9 @@ async fn test_reading_deflate_encoding() { async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -597,9 +608,9 @@ async fn test_reading_deflate_encoding_large_random() { .collect::(); let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -619,9 +630,9 @@ async fn test_reading_deflate_encoding_large_random() { #[actix_rt::test] async fn test_brotli_encoding() { let srv = actix_test::start_with(actix_test::config().h1(), || { - App::new().service( - web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) + App::new().service(web::resource("/").route(web::to(move |body: Bytes| async { + HttpResponse::Ok().body(body) + }))) }); let request = srv @@ -649,7 +660,7 @@ async fn test_brotli_encoding_large() { App::new().service( web::resource("/") .app_data(web::PayloadConfig::new(320_000)) - .route(web::to(move |body: Bytes| { + .route(web::to(move |body: Bytes| async { HttpResponse::Ok().streaming(TestBody::new(body, 10240)) })), ) @@ -676,7 +687,7 @@ async fn test_brotli_encoding_large_openssl() { let data = STR.repeat(10); let srv = actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async { // echo decompressed request body back in response HttpResponse::Ok() .insert_header(header::ContentEncoding::Identity) @@ -738,7 +749,7 @@ mod plus_rustls { .collect::(); let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async { // echo decompressed request body back in response HttpResponse::Ok() .insert_header(header::ContentEncoding::Identity) @@ -770,7 +781,7 @@ async fn test_server_cookies() { use actix_web::http; let srv = actix_test::start(|| { - App::new().default_service(web::to(|| { + App::new().default_service(web::to(|| async { HttpResponse::Ok() .cookie( Cookie::build("first", "first_value") @@ -911,7 +922,7 @@ async fn test_accept_encoding_no_match() { let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::default()) - .service(web::resource("/").route(web::to(move || HttpResponse::Ok().finish()))) + .service(web::resource("/").route(web::to(HttpResponse::Ok))) }); let mut res = srv From 5454699babf31859f69b18137285a0729651009b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 24 Jan 2022 11:56:23 +0000 Subject: [PATCH 309/861] propagate response error in all necessary places --- src/response/response.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/response/response.rs b/src/response/response.rs index 0469c5ce9..6449c586d 100644 --- a/src/response/response.rs +++ b/src/response/response.rs @@ -238,7 +238,7 @@ impl HttpResponse { ( HttpResponse { res: head, - error: None, + error: self.error, }, body, ) @@ -248,7 +248,7 @@ impl HttpResponse { pub fn drop_body(self) -> HttpResponse<()> { HttpResponse { res: self.res.drop_body(), - error: None, + error: self.error, } } From 1bd2076b35f9c9bcc6ef9fe96f157c4de0cbd7cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 25 Jan 2022 16:44:05 +0000 Subject: [PATCH 310/861] prevent drive traversal in windows --- actix-files/src/path_buf.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index f7f7cdab6..9ee1338c6 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -59,6 +59,8 @@ impl PathBufWrap { continue; } else if cfg!(windows) && segment.contains('\\') { return Err(UriSegmentError::BadChar('\\')); + } else if cfg!(windows) && segment.contains(':') { + return Err(UriSegmentError::BadChar(':')); } else { buf.push(segment) } @@ -66,7 +68,11 @@ impl PathBufWrap { // make sure we agree with stdlib parser for (i, component) in buf.components().enumerate() { - assert!(matches!(component, Component::Normal(_))); + assert!( + matches!(component, Component::Normal(_)), + "component `{:?}` is not normal", + component + ); assert!(i < segment_count); } @@ -159,4 +165,26 @@ mod tests { PathBuf::from_iter(vec!["etc/passwd"]) ); } + + #[test] + #[cfg_attr(windows, should_panic)] + fn windows_drive_traversal() { + // detect issues in windows that could lead to path traversal + // see Date: Thu, 27 Jan 2022 06:06:55 +0000 Subject: [PATCH 311/861] move dispatcher tests to own file --- actix-http/src/h1/dispatcher.rs | 406 +---------------------- actix-http/src/h1/dispatcher_tests.rs | 460 ++++++++++++++++++++++++++ actix-http/src/h1/mod.rs | 2 + actix-http/src/test.rs | 55 ++- 4 files changed, 511 insertions(+), 412 deletions(-) create mode 100644 actix-http/src/h1/dispatcher_tests.rs diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 13055f08a..5b790469f 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -89,16 +89,16 @@ pin_project! { U::Error: fmt::Display, { #[pin] - inner: DispatcherState, + pub(super) inner: DispatcherState, // used in tests - poll_count: u64, + pub(super) poll_count: u64, } } pin_project! { #[project = DispatcherStateProj] - enum DispatcherState + pub(super) enum DispatcherState where S: Service, S::Error: Into>, @@ -118,7 +118,7 @@ pin_project! { pin_project! { #[project = InnerDispatcherProj] - struct InnerDispatcher + pub(super) struct InnerDispatcher where S: Service, S::Error: Into>, @@ -132,7 +132,7 @@ pin_project! { U::Error: fmt::Display, { flow: Rc>, - flags: Flags, + pub(super) flags: Flags, peer_addr: Option, conn_data: Option>, error: Option, @@ -146,7 +146,7 @@ pin_project! { #[pin] ka_timer: Option, - io: Option, + pub(super) io: Option, read_buf: BytesMut, write_buf: BytesMut, codec: Codec, @@ -1039,397 +1039,3 @@ where } } } - -#[cfg(test)] -mod tests { - use std::str; - - use actix_service::fn_service; - use actix_utils::future::{ready, Ready}; - use bytes::Bytes; - use futures_util::future::lazy; - - use super::*; - use crate::{ - error::Error, - h1::{ExpectHandler, UpgradeHandler}, - test::{TestBuffer, TestSeqBuffer}, - HttpMessage, KeepAlive, Method, - }; - - fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { - haystack[from..] - .windows(needle.len()) - .position(|window| window == needle) - } - - fn stabilize_date_header(payload: &mut [u8]) { - let mut from = 0; - - while let Some(pos) = find_slice(payload, b"date", from) { - payload[(from + pos)..(from + pos + 35)] - .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); - from += 35; - } - } - - fn ok_service( - ) -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) - } - - fn echo_path_service( - ) -> impl Service, Error = Error> { - fn_service(|req: Request| { - let path = req.path().as_bytes(); - ready(Ok::<_, Error>( - Response::ok().set_body(Bytes::copy_from_slice(path)), - )) - }) - } - - fn echo_payload_service() -> impl Service, Error = Error> - { - fn_service(|mut req: Request| { - Box::pin(async move { - use futures_util::stream::StreamExt as _; - - let mut pl = req.take_payload(); - let mut body = BytesMut::new(); - while let Some(chunk) = pl.next().await { - body.extend_from_slice(chunk.unwrap().chunk()) - } - - Ok::<_, Error>(Response::ok().set_body(body.freeze())) - }) - }) - } - - #[actix_rt::test] - async fn test_req_parse_err() { - lazy(|cx| { - let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - - let services = HttpFlow::new(ok_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - ServiceConfig::default(), - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!(), - Poll::Ready(res) => assert!(res.is_err()), - } - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - assert!(inner.flags.contains(Flags::READ_DISCONNECT)); - assert_eq!( - &inner.project().io.take().unwrap().write_buf[..26], - b"HTTP/1.1 400 Bad Request\r\n" - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_pipelining() { - lazy(|cx| { - let buf = TestBuffer::new( - "\ - GET /abcd HTTP/1.1\r\n\r\n\ - GET /def HTTP/1.1\r\n\r\n\ - ", - ); - - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - cfg, - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!("first poll should not be pending"), - Poll::Ready(res) => assert!(res.is_ok()), - } - - // polls: initial => shutdown - assert_eq!(h1.poll_count, 2); - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - let res = &mut inner.project().io.take().unwrap().write_buf[..]; - stabilize_date_header(res); - - let exp = b"\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /abcd\ - HTTP/1.1 200 OK\r\n\ - content-length: 4\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /def\ - "; - - assert_eq!(res.to_vec(), exp.to_vec()); - } - }) - .await; - - lazy(|cx| { - let buf = TestBuffer::new( - "\ - GET /abcd HTTP/1.1\r\n\r\n\ - GET /def HTTP/1\r\n\r\n\ - ", - ); - - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - services, - cfg, - None, - OnConnectData::default(), - ); - - actix_rt::pin!(h1); - - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - match h1.as_mut().poll(cx) { - Poll::Pending => panic!("first poll should not be pending"), - Poll::Ready(res) => assert!(res.is_err()), - } - - // polls: initial => shutdown - assert_eq!(h1.poll_count, 1); - - if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { - let res = &mut inner.project().io.take().unwrap().write_buf[..]; - stabilize_date_header(res); - - let exp = b"\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /abcd\ - HTTP/1.1 400 Bad Request\r\n\ - content-length: 0\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - "; - - assert_eq!(res.to_vec(), exp.to_vec()); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_expect() { - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - POST /upload HTTP/1.1\r\n\ - Content-Length: 5\r\n\ - Expect: 100-continue\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_pending()); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - // polls: manual - assert_eq!(h1.poll_count, 1); - eprintln!("poll count: {}", h1.poll_count); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let res = &io.write_buf()[..]; - assert_eq!( - str::from_utf8(res).unwrap(), - "HTTP/1.1 100 Continue\r\n\r\n" - ); - } - - buf.extend_read_buf("12345"); - assert!(h1.as_mut().poll(cx).is_ready()); - - // polls: manual manual shutdown - assert_eq!(h1.poll_count, 3); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); - stabilize_date_header(&mut res); - - assert_eq!( - str::from_utf8(&res).unwrap(), - "\ - HTTP/1.1 100 Continue\r\n\ - \r\n\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ - \r\n\ - 12345\ - " - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_eager_expect() { - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - POST /upload HTTP/1.1\r\n\ - Content-Length: 5\r\n\ - Expect: 100-continue\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); - - // polls: manual shutdown - assert_eq!(h1.poll_count, 2); - - if let DispatcherState::Normal { ref inner } = h1.inner { - let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); - stabilize_date_header(&mut res); - - // Despite the content-length header and even though the request payload has not - // been sent, this test expects a complete service response since the payload - // is not used at all. The service passed to dispatcher is path echo and doesn't - // consume payload bytes. - assert_eq!( - str::from_utf8(&res).unwrap(), - "\ - HTTP/1.1 100 Continue\r\n\ - \r\n\ - HTTP/1.1 200 OK\r\n\ - content-length: 7\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ - \r\n\ - /upload\ - " - ); - } - }) - .await; - } - - #[actix_rt::test] - async fn test_upgrade() { - struct TestUpgrade; - - impl Service<(Request, Framed)> for TestUpgrade { - type Response = (); - type Error = Error; - type Future = Ready>; - - actix_service::always_ready!(); - - fn call(&self, (req, _framed): (Request, Framed)) -> Self::Future { - assert_eq!(req.method(), Method::GET); - assert!(req.upgrade()); - assert_eq!(req.headers().get("upgrade").unwrap(), "websocket"); - ready(Ok(())) - } - } - - lazy(|cx| { - let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - - let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); - - let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - - buf.extend_read_buf( - "\ - GET /ws HTTP/1.1\r\n\ - Connection: Upgrade\r\n\ - Upgrade: websocket\r\n\ - \r\n\ - ", - ); - - actix_rt::pin!(h1); - - assert!(h1.as_mut().poll(cx).is_ready()); - assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); - - // polls: manual shutdown - assert_eq!(h1.poll_count, 2); - }) - .await; - } -} diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs new file mode 100644 index 000000000..21da2422f --- /dev/null +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -0,0 +1,460 @@ +use std::{future::Future, str, task::Poll}; + +use actix_service::fn_service; +use actix_utils::future::{ready, Ready}; +use bytes::Bytes; +use futures_util::future::lazy; + +use actix_codec::Framed; +use actix_service::Service; +use bytes::{Buf, BytesMut}; + +use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags}; +use crate::{ + body::MessageBody, + config::ServiceConfig, + h1::{Codec, ExpectHandler, UpgradeHandler}, + service::HttpFlow, + test::{TestBuffer, TestSeqBuffer}, + Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, +}; + +fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { + memchr::memmem::find(&haystack[from..], needle) +} + +fn stabilize_date_header(payload: &mut [u8]) { + let mut from = 0; + while let Some(pos) = find_slice(payload, b"date", from) { + payload[(from + pos)..(from + pos + 35)] + .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); + from += 35; + } +} + +fn ok_service() -> impl Service, Error = Error> { + fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) +} + +fn echo_path_service( +) -> impl Service, Error = Error> { + fn_service(|req: Request| { + let path = req.path().as_bytes(); + ready(Ok::<_, Error>( + Response::ok().set_body(Bytes::copy_from_slice(path)), + )) + }) +} + +fn echo_payload_service() -> impl Service, Error = Error> { + fn_service(|mut req: Request| { + Box::pin(async move { + use futures_util::stream::StreamExt as _; + + let mut pl = req.take_payload(); + let mut body = BytesMut::new(); + while let Some(chunk) = pl.next().await { + body.extend_from_slice(chunk.unwrap().chunk()) + } + + Ok::<_, Error>(Response::ok().set_body(body.freeze())) + }) + }) +} + +#[actix_rt::test] +#[ignore] +async fn test_keep_alive() { + lazy(|cx| { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should prevent poll from resolving" + ); + + // polls: initial + assert_eq!(h1.poll_count, 1); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn test_req_parse_err() { + lazy(|cx| { + let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); + + let services = HttpFlow::new(ok_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + ServiceConfig::default(), + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!(), + Poll::Ready(res) => assert!(res.is_err()), + } + + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { + assert!(inner.flags.contains(Flags::READ_DISCONNECT)); + assert_eq!( + &buf.write_buf_slice()[..26], + b"HTTP/1.1 400 Bad Request\r\n" + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn pipelining_ok_then_ok() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1.1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /def\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn pipelining_ok_then_bad() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + actix_rt::pin!(h1); + + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_err()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 1); + + let mut res = buf.write_buf_slice_mut(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 400 Bad Request\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + +#[actix_rt::test] +async fn test_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_pending()); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + // polls: manual + assert_eq!(h1.poll_count, 1); + eprintln!("poll count: {}", h1.poll_count); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let res = &io.write_buf()[..]; + assert_eq!( + str::from_utf8(res).unwrap(), + "HTTP/1.1 100 Continue\r\n\r\n" + ); + } + + buf.extend_read_buf("12345"); + assert!(h1.as_mut().poll(cx).is_ready()); + + // polls: manual manual shutdown + assert_eq!(h1.poll_count, 3); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + 12345\ + " + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_eager_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal { ref inner } = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + // Despite the content-length header and even though the request payload has not + // been sent, this test expects a complete service response since the payload + // is not used at all. The service passed to dispatcher is path echo and doesn't + // consume payload bytes. + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 7\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + /upload\ + " + ); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_upgrade() { + struct TestUpgrade; + + impl Service<(Request, Framed)> for TestUpgrade { + type Response = (); + type Error = Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, (req, _framed): (Request, Framed)) -> Self::Future { + assert_eq!(req.method(), Method::GET); + assert!(req.upgrade()); + assert_eq!(req.headers().get("upgrade").unwrap(), "websocket"); + ready(Ok(())) + } + } + + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); + + let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + + buf.extend_read_buf( + "\ + GET /ws HTTP/1.1\r\n\ + Connection: Upgrade\r\n\ + Upgrade: websocket\r\n\ + \r\n\ + ", + ); + + actix_rt::pin!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + }) + .await; +} diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 64586a2dc..8c569165d 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -7,6 +7,8 @@ mod client; mod codec; mod decoder; mod dispatcher; +#[cfg(test)] +mod dispatcher_tests; mod encoder; mod expect; mod payload; diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 1f76498ef..529197736 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,7 +1,7 @@ //! Various testing helpers for use in internal and app tests. use std::{ - cell::{Ref, RefCell}, + cell::{Ref, RefCell, RefMut}, io::{self, Read, Write}, pin::Pin, rc::Rc, @@ -157,10 +157,11 @@ fn parts(parts: &mut Option) -> &mut Inner { } /// Async I/O test buffer. +#[derive(Debug)] pub struct TestBuffer { - pub read_buf: BytesMut, - pub write_buf: BytesMut, - pub err: Option, + pub read_buf: Rc>, + pub write_buf: Rc>, + pub err: Option>, } impl TestBuffer { @@ -170,34 +171,64 @@ impl TestBuffer { T: Into, { Self { - read_buf: data.into(), - write_buf: BytesMut::new(), + read_buf: Rc::new(RefCell::new(data.into())), + write_buf: Rc::new(RefCell::new(BytesMut::new())), err: None, } } + // intentionally not using Clone trait + #[allow(dead_code)] + pub(crate) fn clone(&self) -> Self { + Self { + read_buf: self.read_buf.clone(), + write_buf: self.write_buf.clone(), + err: self.err.clone(), + } + } + /// Create new empty `TestBuffer` instance. pub fn empty() -> Self { Self::new("") } + #[allow(dead_code)] + pub(crate) fn read_buf_slice(&self) -> Ref<'_, [u8]> { + Ref::map(self.read_buf.borrow(), |b| b.as_ref()) + } + + #[allow(dead_code)] + pub(crate) fn read_buf_slice_mut(&self) -> RefMut<'_, [u8]> { + RefMut::map(self.read_buf.borrow_mut(), |b| b.as_mut()) + } + + #[allow(dead_code)] + pub(crate) fn write_buf_slice(&self) -> Ref<'_, [u8]> { + Ref::map(self.write_buf.borrow(), |b| b.as_ref()) + } + + #[allow(dead_code)] + pub(crate) fn write_buf_slice_mut(&self) -> RefMut<'_, [u8]> { + RefMut::map(self.write_buf.borrow_mut(), |b| b.as_mut()) + } + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { - self.read_buf.extend_from_slice(data.as_ref()) + self.read_buf.borrow_mut().extend_from_slice(data.as_ref()) } } impl io::Read for TestBuffer { fn read(&mut self, dst: &mut [u8]) -> Result { - if self.read_buf.is_empty() { + if self.read_buf.borrow().is_empty() { if self.err.is_some() { - Err(self.err.take().unwrap()) + Err(Rc::try_unwrap(self.err.take().unwrap()).unwrap()) } else { Err(io::Error::new(io::ErrorKind::WouldBlock, "")) } } else { - let size = std::cmp::min(self.read_buf.len(), dst.len()); - let b = self.read_buf.split_to(size); + let size = std::cmp::min(self.read_buf.borrow().len(), dst.len()); + let b = self.read_buf.borrow_mut().split_to(size); dst[..size].copy_from_slice(&b); Ok(size) } @@ -206,7 +237,7 @@ impl io::Read for TestBuffer { impl io::Write for TestBuffer { fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_buf.extend(buf); + RefCell::borrow_mut(&self.write_buf).extend(buf); Ok(buf.len()) } From 3ae4f0a62915120172dc4d11e508ce247abde78a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:29:46 +0000 Subject: [PATCH 312/861] add keep-alive dispatcher tests --- actix-http/src/h1/dispatcher_tests.rs | 181 +++++++++++++++++++++++--- actix-http/src/test.rs | 5 + 2 files changed, 166 insertions(+), 20 deletions(-) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 21da2422f..cc86cbdfd 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -1,5 +1,6 @@ -use std::{future::Future, str, task::Poll}; +use std::{future::Future, str, task::Poll, time::Duration}; +use actix_rt::time::sleep; use actix_service::fn_service; use actix_utils::future::{ready, Ready}; use bytes::Bytes; @@ -63,23 +64,22 @@ fn echo_payload_service() -> impl Service, E } #[actix_rt::test] -#[ignore] -async fn test_keep_alive() { +async fn test_keep_alive_timeout() { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + lazy(|cx| { - let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - - let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); - let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); - - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf.clone(), - services, - cfg, - None, - OnConnectData::default(), - ); - actix_rt::pin!(h1); - assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!( @@ -90,7 +90,7 @@ async fn test_keep_alive() { // polls: initial assert_eq!(h1.poll_count, 1); - let mut res = buf.write_buf_slice_mut(); + let mut res = buf.take_write_buf().to_vec(); stabilize_date_header(&mut res); let res = &res[..]; @@ -105,8 +105,149 @@ async fn test_keep_alive() { res, exp, "\nexpected response not in write buffer:\n\ - response: {:?}\n\ - expected: {:?}", + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; + + // sleep slightly longer than keep-alive timeout + sleep(Duration::from_millis(1100)).await; + + lazy(|cx| { + assert!( + h1.as_mut().poll(cx).is_ready(), + "keep-alive should have resolved", + ); + + // polls: initial => keep-alive wake-up shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { + // connection closed + assert!(inner.flags.contains(Flags::SHUTDOWN)); + assert!(inner.flags.contains(Flags::WRITE_DISCONNECT)); + // and nothing added to write buffer + assert!(buf.write_buf_slice().is_empty()); + } + }) + .await; +} + +#[actix_rt::test] +async fn test_keep_alive_follow_up_req() { + let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Timeout(2), 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should prevent poll from resolving" + ); + + // polls: initial + assert_eq!(h1.poll_count, 1); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; + + // sleep for less than KA timeout + sleep(Duration::from_millis(200)).await; + + lazy(|cx| { + assert!( + h1.as_mut().poll(cx).is_pending(), + "keep-alive should not have resolved dispatcher yet", + ); + + // polls: initial => manual + assert_eq!(h1.poll_count, 2); + + if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() { + // connection not closed + assert!(!inner.flags.contains(Flags::SHUTDOWN)); + assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT)); + // and nothing added to write buffer + assert!(buf.write_buf_slice().is_empty()); + } + }) + .await; + + lazy(|cx| { + buf.extend_read_buf( + "\ + GET /efg HTTP/1.1\r\n\ + Connection: close\r\n\ + \r\n\r\n", + ); + + assert!( + h1.as_mut().poll(cx).is_ready(), + "connection close header should override keep-alive setting", + ); + + // polls: initial => manual => follow-up req => shutdown + assert_eq!(h1.poll_count, 4); + + if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() { + // connection closed + assert!(inner.flags.contains(Flags::SHUTDOWN)); + assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT)); + } + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /efg\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", String::from_utf8_lossy(res), String::from_utf8_lossy(exp) ); diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 529197736..0d4d342ec 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -212,6 +212,11 @@ impl TestBuffer { RefMut::map(self.write_buf.borrow_mut(), |b| b.as_mut()) } + #[allow(dead_code)] + pub(crate) fn take_write_buf(&self) -> Bytes { + self.write_buf.borrow_mut().split().freeze() + } + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { self.read_buf.borrow_mut().extend_from_slice(data.as_ref()) From 0d93a8c273ac30cac9e7af5700bd8090af6a60dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:32:28 +0000 Subject: [PATCH 313/861] add examples readme --- examples/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..163f67b46 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Actix Web Examples + +This folder contain just a few standalone code samples. There is a much larger registry of example projects [in the examples repo](https://github.com/actix/examples). From 37799df978818d180e5aec5f9261042a13587d6d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 06:42:54 +0000 Subject: [PATCH 314/861] add basic dispatcher test --- actix-http/src/h1/dispatcher_tests.rs | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index cc86cbdfd..057ef1583 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -63,6 +63,58 @@ fn echo_payload_service() -> impl Service, E }) } +#[actix_rt::test] +async fn test_basic() { + let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + #[actix_rt::test] async fn test_keep_alive_timeout() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); From cc9ba162f78253ac3fbf77d02c82814b132b64ff Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 27 Jan 2022 17:00:07 +0000 Subject: [PATCH 315/861] add late request dispatcher test --- actix-http/src/h1/dispatcher_tests.rs | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 057ef1583..379019c6f 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -63,6 +63,69 @@ fn echo_payload_service() -> impl Service, E }) } +#[actix_rt::test] +async fn late_request() { + let _ = env_logger::try_init(); + + let mut buf = TestBuffer::empty(); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let services = HttpFlow::new(ok_service(), ExpectHandler, None); + + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf.clone(), + services, + cfg, + None, + OnConnectData::default(), + ); + actix_rt::pin!(h1); + + lazy(|cx| { + assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); + + match h1.as_mut().poll(cx) { + Poll::Ready(_) => panic!("first poll should not be ready"), + Poll::Pending => {} + } + + // polls: initial + assert_eq!(h1.poll_count, 1); + + buf.extend_read_buf("GET /abcd HTTP/1.1\r\nConnection: close\r\n\r\n"); + + match h1.as_mut().poll(cx) { + Poll::Pending => panic!("second poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial pending => handle req => shutdown + assert_eq!(h1.poll_count, 3); + + let mut res = buf.take_write_buf().to_vec(); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(exp) + ); + }) + .await; +} + #[actix_rt::test] async fn test_basic() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); From a9f497d05f259e91370b7c8ac5161adf958f5d13 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Fri, 28 Jan 2022 17:28:16 +0000 Subject: [PATCH 316/861] Guard against broken intra-doc links in CI (#2616) --- .github/workflows/clippy-fmt.yml | 18 ++++++++++++++++++ actix-files/src/named.rs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 9fcb0a561..9f5a0570a 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -5,6 +5,24 @@ on: types: [opened, synchronize, reopened] jobs: + lint-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rust-docs + - name: Check for broken intra-doc links + uses: actions-rs/cargo@v1 + env: + RUSTDOCFLAGS: "-D warnings" + with: + command: doc + args: --no-deps --all-features --workspace + fmt: runs-on: ubuntu-latest steps: diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 14495e660..cb6875065 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -209,6 +209,7 @@ impl NamedFile { Self::from_file(file, path) } + #[allow(rustdoc::broken_intra_doc_links)] /// Attempts to open a file asynchronously in read-only mode. /// /// When the `experimental-io-uring` crate feature is enabled, this will be async. @@ -300,7 +301,7 @@ impl NamedFile { /// Set content encoding for serving this file /// - /// Must be used with [`actix_web::middleware::Compress`] to take effect. + /// Must be used with `actix_web::middleware::Compress` to take effect. #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); From 21a08ca7969e9a08035a4b9e78d8419f3cce3c64 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 28 Jan 2022 20:27:16 +0000 Subject: [PATCH 317/861] tweak set_content_encoding docs --- .github/workflows/clippy-fmt.yml | 36 ++++++++++++++++---------------- actix-files/src/named.rs | 6 ++++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 9f5a0570a..bc2cec145 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -5,24 +5,6 @@ on: types: [opened, synchronize, reopened] jobs: - lint-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rust-docs - - name: Check for broken intra-doc links - uses: actions-rs/cargo@v1 - env: - RUSTDOCFLAGS: "-D warnings" - with: - command: doc - args: --no-deps --all-features --workspace - fmt: runs-on: ubuntu-latest steps: @@ -64,3 +46,21 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --tests --examples --all-features + + lint-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rust-docs + - name: Check for broken intra-doc links + uses: actions-rs/cargo@v1 + env: + RUSTDOCFLAGS: "-D warnings" + with: + command: doc + args: --no-deps --all-features --workspace diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index cb6875065..baf9b5531 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -299,9 +299,11 @@ impl NamedFile { self } - /// Set content encoding for serving this file + /// Sets content encoding for this file. /// - /// Must be used with `actix_web::middleware::Compress` to take effect. + /// This prevents the `Compress` middleware from modifying the file contents and signals to + /// browsers/clients how to decode it. For example, if serving a compressed HTML file (e.g., + /// `index.html.gz`) then use `.set_content_encoding(ContentEncoding::Gzip)`. #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); From a3416112a5010b378dcfaebad3b9c65f8838941e Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Fri, 28 Jan 2022 20:31:54 +0000 Subject: [PATCH 318/861] Improve the documentation for `default_service` (#2614) --- src/app.rs | 17 ++--------------- src/resource.rs | 8 +++++--- src/scope.rs | 3 ++- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/app.rs b/src/app.rs index da33ebc4b..bbf752595 100644 --- a/src/app.rs +++ b/src/app.rs @@ -236,9 +236,9 @@ where self } - /// Default service to be used if no matching resource could be found. + /// Default service that is invoked when no matching resource could be found. /// - /// It is possible to use services like `Resource`, `Route`. + /// You must use a [`Route`] as default service: /// /// ``` /// use actix_web::{web, App, HttpResponse}; @@ -253,19 +253,6 @@ where /// .default_service( /// web::route().to(|| HttpResponse::NotFound())); /// ``` - /// - /// It is also possible to use static files as default service. - /// - /// ``` - /// use actix_web::{web, App, HttpResponse}; - /// - /// let app = App::new() - /// .service( - /// web::resource("/index.html").to(|| HttpResponse::Ok())) - /// .default_service( - /// web::to(|| HttpResponse::NotFound()) - /// ); - /// ``` pub fn default_service(mut self, svc: F) -> Self where F: IntoServiceFactory, diff --git a/src/resource.rs b/src/resource.rs index a0fc19faf..3451eff45 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -312,9 +312,11 @@ where } } - /// Default service to be used if no matching route could be found. - /// By default *405* response get returned. Resource does not use - /// default handler from `App` or `Scope`. + /// Default service to be used if no matching route could be found. + /// You can pass a [`Route`] as default_service. + /// + /// If no default service is specified, a `405 Method Not Allowed` response will be returned to the caller. + /// [`Resource`] does **not** inherit the default handler specified on the parent [`App`](crate::App) or [`Scope`](crate::Scope). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/src/scope.rs b/src/scope.rs index c05ce054d..0bad5c581 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -264,7 +264,8 @@ where /// Default service to be used if no matching route could be found. /// - /// If default resource is not registered, app's default resource is being used. + /// If a default service is not registered, it will fall back to the default service of + /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, From b3e84b5c4bf32fd8aeac8c938f9906ce730a7458 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 28 Jan 2022 20:53:51 +0000 Subject: [PATCH 319/861] tweak default_service docs --- src/app.rs | 12 +++++++----- src/app_service.rs | 2 +- src/resource.rs | 10 ++++++---- src/scope.rs | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index bbf752595..a63cf5d50 100644 --- a/src/app.rs +++ b/src/app.rs @@ -238,8 +238,12 @@ where /// Default service that is invoked when no matching resource could be found. /// - /// You must use a [`Route`] as default service: + /// You can use a [`Route`] as default service. /// + /// If a default service is not registered, an empty `404 Not Found` response will be sent to + /// the client instead. + /// + /// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -248,10 +252,8 @@ where /// } /// /// let app = App::new() - /// .service( - /// web::resource("/index.html").route(web::get().to(index))) - /// .default_service( - /// web::route().to(|| HttpResponse::NotFound())); + /// .service(web::resource("/index.html").route(web::get().to(index))) + /// .default_service(web::to(|| HttpResponse::NotFound())); /// ``` pub fn default_service(mut self, svc: F) -> Self where diff --git a/src/app_service.rs b/src/app_service.rs index edfb3e4a2..dbd718330 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -72,7 +72,7 @@ where }))) }); - // App config + // create App config to pass to child services let mut config = AppService::new(config, default.clone()); // register services diff --git a/src/resource.rs b/src/resource.rs index 3451eff45..6a01a0496 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -312,11 +312,13 @@ where } } - /// Default service to be used if no matching route could be found. - /// You can pass a [`Route`] as default_service. + /// Default service to be used if no matching route could be found. /// - /// If no default service is specified, a `405 Method Not Allowed` response will be returned to the caller. - /// [`Resource`] does **not** inherit the default handler specified on the parent [`App`](crate::App) or [`Scope`](crate::Scope). + /// You can use a [`Route`] as default service. + /// + /// If a default service is not registered, an empty `405 Method Not Allowed` response will be + /// sent to the client instead. Unlike [`Scope`](crate::Scope)s, a [`Resource`] does **not** + /// inherit its parent's default service. pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, diff --git a/src/scope.rs b/src/scope.rs index 0bad5c581..dad727430 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -262,10 +262,10 @@ where ) } - /// Default service to be used if no matching route could be found. + /// Default service to be used if no matching resource could be found. /// /// If a default service is not registered, it will fall back to the default service of - /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service). + /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service)). pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, From 3200de3f34b21f65bf84d7b04ba118f03d808f02 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 17:30:34 +0000 Subject: [PATCH 320/861] fix request head timeout (#2611) --- CHANGES.md | 5 + actix-http/CHANGES.md | 26 + actix-http/Cargo.toml | 1 + actix-http/examples/bench.rs | 27 + actix-http/examples/echo.rs | 6 +- actix-http/examples/h2spec.rs | 25 + actix-http/examples/hello-world.rs | 6 +- actix-http/src/builder.rs | 93 +-- actix-http/src/config.rs | 317 ++------- actix-http/src/date.rs | 92 +++ actix-http/src/h1/client.rs | 49 +- actix-http/src/h1/codec.rs | 24 +- actix-http/src/h1/dispatcher.rs | 752 ++++++++++++++-------- actix-http/src/h1/dispatcher_tests.rs | 103 ++- actix-http/src/h1/encoder.rs | 7 +- actix-http/src/h1/mod.rs | 6 +- actix-http/src/h1/timer.rs | 80 +++ actix-http/src/h2/dispatcher.rs | 16 +- actix-http/src/h2/mod.rs | 18 +- actix-http/src/header/map.rs | 2 +- actix-http/src/header/shared/http_date.rs | 3 +- actix-http/src/keep_alive.rs | 83 +++ actix-http/src/lib.rs | 7 +- actix-http/src/notify_on_drop.rs | 49 ++ actix-http/src/requests/head.rs | 2 +- actix-http/src/responses/head.rs | 24 +- actix-http/src/service.rs | 28 +- actix-http/src/test.rs | 2 +- actix-http/tests/test_client.rs | 8 +- actix-http/tests/test_h2_timer.rs | 8 +- actix-http/tests/test_server.rs | 95 +-- actix-http/tests/test_ws.rs | 2 +- actix-test/CHANGES.md | 3 + actix-test/src/lib.rs | 30 +- awc/src/client/h1proto.rs | 6 +- src/server.rs | 51 +- tests/test_httpserver.rs | 6 +- tests/test_server.rs | 8 +- 38 files changed, 1303 insertions(+), 767 deletions(-) create mode 100644 actix-http/examples/bench.rs create mode 100644 actix-http/examples/h2spec.rs create mode 100644 actix-http/src/date.rs create mode 100644 actix-http/src/h1/timer.rs create mode 100644 actix-http/src/keep_alive.rs create mode 100644 actix-http/src/notify_on_drop.rs diff --git a/CHANGES.md b/CHANGES.md index 8c3997663..c00bc7198 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] +- Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] + ### Removed - `impl Future for HttpResponse`. [#2601] [#2601]: https://github.com/actix/actix-web/pull/2601 +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 4.0.0-beta.21 - 2022-01-21 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6047a6bc5..a748bc43f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,32 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- Implement `Default` for `KeepAlive`. [#2611] +- Implement `From` for `KeepAlive`. [#2611] +- Implement `From>` for `KeepAlive`. [#2611] +- Implement `Default` for `HttpServiceBuilder`. [#2611] + +### Changed +- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] +- Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611] +- Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611] +- Rename `h1::Codec::{keepalive => keep_alive}`. [#2611] +- Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611] +- Rename `h1::ClientCodec::{keepalive => keep_alive}`. [#2611] +- Rename `h1::ClientPayloadCodec::{keepalive => keep_alive}`. [#2611] +- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611] + +### Fixed +- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611] + +### Removed +- `ServiceConfig::{client_timer, keep_alive_timer}`. [#2611] +- `impl From for KeepAlive`; use `Duration`s instead. [#2611] +- `impl From> for KeepAlive`; use `Duration`s instead. [#2611] +- `HttpServiceBuilder::new`; use `default` instead. [#2611] + +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 3.0.0-beta.19 - 2022-01-21 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e93d1b7af..11bfa7a1a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -92,6 +92,7 @@ criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" +once_cell = "1.9" rcgen = "0.8" regex = "1.3" rustls-pemfile = "0.2" diff --git a/actix-http/examples/bench.rs b/actix-http/examples/bench.rs new file mode 100644 index 000000000..e41c0bb4f --- /dev/null +++ b/actix-http/examples/bench.rs @@ -0,0 +1,27 @@ +use std::{convert::Infallible, io, time::Duration}; + +use actix_http::{HttpService, Request, Response, StatusCode}; +use actix_server::Server; +use once_cell::sync::Lazy; + +static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(20)); + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("dispatcher-benchmark", ("127.0.0.1", 8080), || { + HttpService::build() + .client_request_timeout(Duration::from_secs(1)) + .finish(|_: Request| async move { + let mut res = Response::build(StatusCode::OK); + Ok::<_, Infallible>(res.body(&**STR)) + }) + .tcp() + })? + // limiting number of workers so that bench client is not sharing as many resources + .workers(4) + .run() + .await +} diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index f9188ed9f..58de64530 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, time::Duration}; use actix_http::{Error, HttpService, Request, Response, StatusCode}; use actix_server::Server; @@ -13,8 +13,8 @@ async fn main() -> io::Result<()> { Server::build() .bind("echo", ("127.0.0.1", 8080), || { HttpService::build() - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) // handles HTTP/1.1 and HTTP/2 .finish(|mut req: Request| async move { let mut body = BytesMut::new(); diff --git a/actix-http/examples/h2spec.rs b/actix-http/examples/h2spec.rs new file mode 100644 index 000000000..4ab426c6c --- /dev/null +++ b/actix-http/examples/h2spec.rs @@ -0,0 +1,25 @@ +use std::{convert::Infallible, io}; + +use actix_http::{HttpService, Request, Response, StatusCode}; +use actix_server::Server; +use once_cell::sync::Lazy; + +static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(100)); + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("h2spec", ("127.0.0.1", 8080), || { + HttpService::build() + .h2(|_: Request| async move { + let mut res = Response::build(StatusCode::OK); + Ok::<_, Infallible>(res.body(&**STR)) + }) + .tcp() + })? + .workers(4) + .run() + .await +} diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a29903cc4..1a83d4d9c 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, io}; +use std::{convert::Infallible, io, time::Duration}; use actix_http::{ header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, @@ -12,8 +12,8 @@ async fn main() -> io::Result<()> { Server::build() .bind("hello-world", ("127.0.0.1", 8080), || { HttpService::build() - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .on_connect_ext(|_, ext| { ext.insert(42u32); }) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 408ee7924..9dd145ce1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,25 +1,23 @@ -use std::{fmt, marker::PhantomData, net, rc::Rc}; +use std::{fmt, marker::PhantomData, net, rc::Rc, time::Duration}; use actix_codec::Framed; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ body::{BoxBody, MessageBody}, - config::{KeepAlive, ServiceConfig}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, h2::H2Service, service::HttpService, - ConnectCallback, Extensions, Request, Response, + ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig, }; -/// A HTTP service builder +/// An HTTP service builder. /// -/// This type can be used to construct an instance of [`HttpService`] through a -/// builder-like pattern. +/// This type can construct an instance of [`HttpService`] through a builder-like pattern. pub struct HttpServiceBuilder { keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, expect: X, @@ -28,22 +26,23 @@ pub struct HttpServiceBuilder { _phantom: PhantomData, } -impl HttpServiceBuilder +impl Default for HttpServiceBuilder where S: ServiceFactory, S::Error: Into> + 'static, S::InitError: fmt::Debug, >::Future: 'static, { - /// Create instance of `ServiceConfigBuilder` - #[allow(clippy::new_without_default)] - pub fn new() -> Self { + fn default() -> Self { HttpServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, + // ServiceConfig parts (make sure defaults match) + keep_alive: KeepAlive::default(), + client_request_timeout: Duration::from_secs(5), + client_disconnect_timeout: Duration::ZERO, secure: false, local_addr: None, + + // dispatcher parts expect: ExpectHandler, upgrade: None, on_connect_ext: None, @@ -65,9 +64,11 @@ where U::Error: fmt::Display, U::InitError: fmt::Debug, { - /// Set server keep-alive setting. + /// Set connection keep-alive setting. /// - /// By default keep alive is set to a 5 seconds. + /// Applies to HTTP/1.1 keep-alive and HTTP/2 ping-pong. + /// + /// By default keep-alive is 5 seconds. pub fn keep_alive>(mut self, val: W) -> Self { self.keep_alive = val.into(); self @@ -85,33 +86,45 @@ where self } - /// Set server client timeout in milliseconds for first request. + /// Set client request timeout (for first request). /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. + /// Defines a timeout for reading client request header. If the client does not transmit the + /// request head within this duration, the connection is terminated with a `408 Request Timeout` + /// response error. /// - /// To disable timeout set value to 0. + /// A duration of zero disables the timeout. /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; + /// By default, the client timeout is 5 seconds. + pub fn client_request_timeout(mut self, dur: Duration) -> Self { + self.client_request_timeout = dur; self } - /// Set server connection disconnect timeout in milliseconds. + #[doc(hidden)] + #[deprecated(since = "3.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_timeout(self, dur: Duration) -> Self { + self.client_request_timeout(dur) + } + + /// Set client connection disconnect timeout. /// /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete /// within this time, the request get dropped. This timeout affects secure connections. /// - /// To disable timeout set value to 0. + /// A duration of zero disables the timeout. /// - /// By default disconnect timeout is set to 0. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; + /// By default, the disconnect timeout is disabled. + pub fn client_disconnect_timeout(mut self, dur: Duration) -> Self { + self.client_disconnect_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")] + pub fn client_disconnect(self, dur: Duration) -> Self { + self.client_disconnect_timeout(dur) + } + /// Provide service for `EXPECT: 100-Continue` support. /// /// Service get called with request that contains `EXPECT` header. @@ -126,8 +139,8 @@ where { HttpServiceBuilder { keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, + client_request_timeout: self.client_request_timeout, + client_disconnect_timeout: self.client_disconnect_timeout, secure: self.secure, local_addr: self.local_addr, expect: expect.into_factory(), @@ -150,8 +163,8 @@ where { HttpServiceBuilder { keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, + client_request_timeout: self.client_request_timeout, + client_disconnect_timeout: self.client_disconnect_timeout, secure: self.secure, local_addr: self.local_addr, expect: self.expect, @@ -185,8 +198,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); @@ -209,8 +222,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); @@ -230,8 +243,8 @@ where { let cfg = ServiceConfig::new( self.keep_alive, - self.client_timeout, - self.client_disconnect, + self.client_request_timeout, + self.client_disconnect_timeout, self.secure, self.local_addr, ); diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index b6d5a7d51..aa05d6aba 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,71 +1,36 @@ use std::{ - cell::Cell, - fmt::{self, Write}, net, rc::Rc, - time::{Duration, SystemTime}, + time::{Duration, Instant}, }; -use actix_rt::{ - task::JoinHandle, - time::{interval, sleep_until, Instant, Sleep}, -}; use bytes::BytesMut; -/// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub(crate) const DATE_VALUE_LENGTH: usize = 29; +use crate::{date::DateService, KeepAlive}; -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - - /// Rely on OS to shutdown tcp connection - Os, - - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -/// Http service configuration +/// HTTP service configuration. +#[derive(Debug, Clone)] pub struct ServiceConfig(Rc); +#[derive(Debug)] struct Inner { - keep_alive: Option, - client_timeout: u64, - client_disconnect: u64, - ka_enabled: bool, + keep_alive: KeepAlive, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, date_service: DateService, } -impl Clone for ServiceConfig { - fn clone(&self) -> Self { - ServiceConfig(self.0.clone()) - } -} - impl Default for ServiceConfig { fn default() -> Self { - Self::new(KeepAlive::Timeout(5), 0, 0, false, None) + Self::new( + KeepAlive::default(), + Duration::from_secs(5), + Duration::ZERO, + false, + None, + ) } } @@ -73,34 +38,22 @@ impl ServiceConfig { /// Create instance of `ServiceConfig` pub fn new( keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, secure: bool, local_addr: Option, ) -> ServiceConfig { - let (keep_alive, ka_enabled) = match keep_alive { - KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os => (0, true), - KeepAlive::Disabled => (0, false), - }; - let keep_alive = if ka_enabled && keep_alive > 0 { - Some(Duration::from_secs(keep_alive)) - } else { - None - }; - ServiceConfig(Rc::new(Inner { - keep_alive, - ka_enabled, - client_timeout, - client_disconnect, + keep_alive: keep_alive.normalize(), + client_request_timeout, + client_disconnect_timeout, secure, local_addr, date_service: DateService::new(), })) } - /// Returns true if connection is secure (HTTPS) + /// Returns `true` if connection is secure (i.e., using TLS / HTTPS). #[inline] pub fn secure(&self) -> bool { self.0.secure @@ -114,239 +67,91 @@ impl ServiceConfig { self.0.local_addr } - /// Keep alive duration if configured. + /// Connection keep-alive setting. #[inline] - pub fn keep_alive(&self) -> Option { + pub fn keep_alive(&self) -> KeepAlive { self.0.keep_alive } - /// Return state of connection keep-alive functionality - #[inline] - pub fn keep_alive_enabled(&self) -> bool { - self.0.ka_enabled - } - - /// Client timeout for first request. - #[inline] - pub fn client_timer(&self) -> Option { - let delay_time = self.0.client_timeout; - if delay_time != 0 { - Some(sleep_until(self.now() + Duration::from_millis(delay_time))) - } else { - None + /// Creates a time object representing the deadline for this connection's keep-alive period, if + /// enabled. + /// + /// When [`KeepAlive::Os`] or [`KeepAlive::Disabled`] is set, this will return `None`. + pub fn keep_alive_deadline(&self) -> Option { + match self.keep_alive() { + KeepAlive::Timeout(dur) => Some(self.now() + dur), + KeepAlive::Os => None, + KeepAlive::Disabled => None, } } - /// Client timeout for first request. - pub fn client_timer_expire(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } + /// Creates a time object representing the deadline for the client to finish sending the head of + /// its first request. + /// + /// Returns `None` if this `ServiceConfig was` constructed with `client_request_timeout: 0`. + pub fn client_request_deadline(&self) -> Option { + let timeout = self.0.client_request_timeout; + (timeout != Duration::ZERO).then(|| self.now() + timeout) } - /// Client disconnect timer - pub fn client_disconnect_timer(&self) -> Option { - let delay = self.0.client_disconnect; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } + /// Creates a time object representing the deadline for the client to disconnect. + pub fn client_disconnect_deadline(&self) -> Option { + let timeout = self.0.client_disconnect_timeout; + (timeout != Duration::ZERO).then(|| self.now() + timeout) } - /// Return keep-alive timer delay is configured. - #[inline] - pub fn keep_alive_timer(&self) -> Option { - self.keep_alive().map(|ka| sleep_until(self.now() + ka)) - } - - /// Keep-alive expire time - pub fn keep_alive_expire(&self) -> Option { - self.keep_alive().map(|ka| self.now() + ka) - } - - #[inline] pub(crate) fn now(&self) -> Instant { self.0.date_service.now() } - #[doc(hidden)] - pub fn set_date(&self, dst: &mut BytesMut, camel_case: bool) { + pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); self.0 .date_service - .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); + .with_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } - pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { + pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service - .set_date(|date| dst.extend_from_slice(&date.bytes)); - } -} - -#[derive(Copy, Clone)] -struct Date { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -impl Date { - fn new() -> Date { - let mut date = Date { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - }; - date.update(); - date - } - - fn update(&mut self) { - self.pos = 0; - write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); - } -} - -impl fmt::Write for Date { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - -/// Service for update Date and Instant periodically at 500 millis interval. -struct DateService { - current: Rc>, - handle: JoinHandle<()>, -} - -impl Drop for DateService { - fn drop(&mut self) { - // stop the timer update async task on drop. - self.handle.abort(); - } -} - -impl DateService { - fn new() -> Self { - // shared date and timer for DateService and update async task. - let current = Rc::new(Cell::new((Date::new(), Instant::now()))); - let current_clone = Rc::clone(¤t); - // spawn an async task sleep for 500 milli and update current date/timer in a loop. - // handle is used to stop the task on DateService drop. - let handle = actix_rt::spawn(async move { - #[cfg(test)] - let _notify = notify_on_drop::NotifyOnDrop::new(); - - let mut interval = interval(Duration::from_millis(500)); - loop { - let now = interval.tick().await; - let date = Date::new(); - current_clone.set((date, now)); - } - }); - - DateService { current, handle } - } - - fn now(&self) -> Instant { - self.current.get().1 - } - - fn set_date(&self, mut f: F) { - f(&self.current.get().0); - } -} - -// TODO: move to a util module for testing all spawn handle drop style tasks. -/// Test Module for checking the drop state of certain async tasks that are spawned -/// with `actix_rt::spawn` -/// -/// The target task must explicitly generate `NotifyOnDrop` when spawn the task -#[cfg(test)] -mod notify_on_drop { - use std::cell::RefCell; - - thread_local! { - static NOTIFY_DROPPED: RefCell> = RefCell::new(None); - } - - /// Check if the spawned task is dropped. - /// - /// # Panics - /// Panics when there was no `NotifyOnDrop` instance on current thread. - pub(crate) fn is_dropped() -> bool { - NOTIFY_DROPPED.with(|bool| { - bool.borrow() - .expect("No NotifyOnDrop existed on current thread") - }) - } - - pub(crate) struct NotifyOnDrop; - - impl NotifyOnDrop { - /// # Panic: - /// - /// When construct multiple instances on any given thread. - pub(crate) fn new() -> Self { - NOTIFY_DROPPED.with(|bool| { - let mut bool = bool.borrow_mut(); - if bool.is_some() { - panic!("NotifyOnDrop existed on current thread"); - } else { - *bool = Some(false); - } - }); - - NotifyOnDrop - } - } - - impl Drop for NotifyOnDrop { - fn drop(&mut self) { - NOTIFY_DROPPED.with(|bool| { - if let Some(b) = bool.borrow_mut().as_mut() { - *b = true; - } - }); - } + .with_date(|date| dst.extend_from_slice(&date.bytes)); } } #[cfg(test)] mod tests { use super::*; + use crate::{date::DATE_VALUE_LENGTH, notify_on_drop}; - use actix_rt::{task::yield_now, time::sleep}; + use actix_rt::{ + task::yield_now, + time::{sleep, sleep_until}, + }; use memchr::memmem; #[actix_rt::test] async fn test_date_service_update() { - let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); + let settings = + ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None); yield_now().await; let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let now1 = settings.now(); - sleep_until(Instant::now() + Duration::from_secs(2)).await; + sleep_until((Instant::now() + Duration::from_secs(2)).into()).await; yield_now().await; let now2 = settings.now(); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_ne!(now1, now2); @@ -402,10 +207,10 @@ mod tests { let settings = ServiceConfig::default(); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_eq!(buf1, buf2); } @@ -415,11 +220,11 @@ mod tests { let settings = ServiceConfig::default(); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, false); + settings.write_date_header(&mut buf, false); assert!(memmem::find(&buf, b"date:").is_some()); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, true); + settings.write_date_header(&mut buf, true); assert!(memmem::find(&buf, b"Date:").is_some()); } } diff --git a/actix-http/src/date.rs b/actix-http/src/date.rs new file mode 100644 index 000000000..1358bbd8c --- /dev/null +++ b/actix-http/src/date.rs @@ -0,0 +1,92 @@ +use std::{ + cell::Cell, + fmt::{self, Write}, + rc::Rc, + time::{Duration, Instant, SystemTime}, +}; + +use actix_rt::{task::JoinHandle, time::interval}; + +/// "Thu, 01 Jan 1970 00:00:00 GMT".len() +pub(crate) const DATE_VALUE_LENGTH: usize = 29; + +#[derive(Clone, Copy)] +pub(crate) struct Date { + pub(crate) bytes: [u8; DATE_VALUE_LENGTH], + pos: usize, +} + +impl Date { + fn new() -> Date { + let mut date = Date { + bytes: [0; DATE_VALUE_LENGTH], + pos: 0, + }; + date.update(); + date + } + + fn update(&mut self) { + self.pos = 0; + write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); + } +} + +impl fmt::Write for Date { + fn write_str(&mut self, s: &str) -> fmt::Result { + let len = s.len(); + self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); + self.pos += len; + Ok(()) + } +} + +/// Service for update Date and Instant periodically at 500 millis interval. +pub(crate) struct DateService { + current: Rc>, + handle: JoinHandle<()>, +} + +impl DateService { + pub(crate) fn new() -> Self { + // shared date and timer for DateService and update async task. + let current = Rc::new(Cell::new((Date::new(), Instant::now()))); + let current_clone = Rc::clone(¤t); + // spawn an async task sleep for 500 millis and update current date/timer in a loop. + // handle is used to stop the task on DateService drop. + let handle = actix_rt::spawn(async move { + #[cfg(test)] + let _notify = crate::notify_on_drop::NotifyOnDrop::new(); + + let mut interval = interval(Duration::from_millis(500)); + loop { + let now = interval.tick().await; + let date = Date::new(); + current_clone.set((date, now.into_std())); + } + }); + + DateService { current, handle } + } + + pub(crate) fn now(&self) -> Instant { + self.current.get().1 + } + + pub(crate) fn with_date(&self, mut f: F) { + f(&self.current.get().0); + } +} + +impl fmt::Debug for DateService { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DateService").finish_non_exhaustive() + } +} + +impl Drop for DateService { + fn drop(&mut self) { + // stop the timer update async task on drop. + self.handle.abort(); + } +} diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 9bd896ae0..4e0ae8f48 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{fmt, io}; use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; @@ -17,9 +17,9 @@ use crate::{ bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_1000; - const STREAM = 0b0001_0000; + const HEAD = 0b0000_0001; + const KEEP_ALIVE_ENABLED = 0b0000_1000; + const STREAM = 0b0001_0000; } } @@ -38,7 +38,7 @@ struct ClientCodecInner { decoder: decoder::MessageDecoder, payload: Option, version: Version, - ctype: ConnectionType, + conn_type: ConnectionType, // encoder part flags: Flags, @@ -51,23 +51,32 @@ impl Default for ClientCodec { } } +impl fmt::Debug for ClientCodec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("h1::ClientCodec") + .field("flags", &self.inner.flags) + .finish_non_exhaustive() + } +} + impl ClientCodec { /// Create HTTP/1 codec. /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED + let flags = if config.keep_alive().enabled() { + Flags::KEEP_ALIVE_ENABLED } else { Flags::empty() }; + ClientCodec { inner: ClientCodecInner { config, decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, - ctype: ConnectionType::Close, + conn_type: ConnectionType::Close, flags, encoder: encoder::MessageEncoder::default(), @@ -77,12 +86,12 @@ impl ClientCodec { /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.inner.ctype == ConnectionType::Upgrade + self.inner.conn_type == ConnectionType::Upgrade } /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive + pub fn keep_alive(&self) -> bool { + self.inner.conn_type == ConnectionType::KeepAlive } /// Check last request's message type @@ -104,8 +113,8 @@ impl ClientCodec { impl ClientPayloadCodec { /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive + pub fn keep_alive(&self) -> bool { + self.inner.conn_type == ConnectionType::KeepAlive } /// Transform payload codec to a message codec @@ -122,12 +131,12 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.conn_type() { + if let Some(conn_type) = req.conn_type() { // do not use peer's keep-alive - self.inner.ctype = if ctype == ConnectionType::KeepAlive { - self.inner.ctype + self.inner.conn_type = if conn_type == ConnectionType::KeepAlive { + self.inner.conn_type } else { - ctype + conn_type }; } @@ -192,9 +201,9 @@ impl Encoder> for ClientCodec { .set(Flags::HEAD, head.as_ref().method == Method::HEAD); // connection status - inner.ctype = match head.as_ref().connection_type() { + inner.conn_type = match head.as_ref().connection_type() { ConnectionType::KeepAlive => { - if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + if inner.flags.contains(Flags::KEEP_ALIVE_ENABLED) { ConnectionType::KeepAlive } else { ConnectionType::Close @@ -211,7 +220,7 @@ impl Encoder> for ClientCodec { false, inner.version, length, - inner.ctype, + inner.conn_type, &inner.config, )?; } diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 9a8907579..df74bcc42 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -15,9 +15,9 @@ use crate::{ bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const STREAM = 0b0000_0100; + const HEAD = 0b0000_0001; + const KEEP_ALIVE_ENABLED = 0b0000_0010; + const STREAM = 0b0000_0100; } } @@ -42,7 +42,9 @@ impl Default for Codec { impl fmt::Debug for Codec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "h1::Codec({:?})", self.flags) + f.debug_struct("h1::Codec") + .field("flags", &self.flags) + .finish_non_exhaustive() } } @@ -51,8 +53,8 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED + let flags = if config.keep_alive().enabled() { + Flags::KEEP_ALIVE_ENABLED } else { Flags::empty() }; @@ -76,14 +78,14 @@ impl Codec { /// Check if last response is keep-alive. #[inline] - pub fn keepalive(&self) -> bool { + pub fn keep_alive(&self) -> bool { self.conn_type == ConnectionType::KeepAlive } /// Check if keep-alive enabled on server level. #[inline] - pub fn keepalive_enabled(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE_ENABLED) + pub fn keep_alive_enabled(&self) -> bool { + self.flags.contains(Flags::KEEP_ALIVE_ENABLED) } /// Check last request's message type. @@ -124,7 +126,7 @@ impl Decoder for Codec { self.version = head.version; self.conn_type = head.connection_type(); if self.conn_type == ConnectionType::KeepAlive - && !self.flags.contains(Flags::KEEPALIVE_ENABLED) + && !self.flags.contains(Flags::KEEP_ALIVE_ENABLED) { self.conn_type = ConnectionType::Close } @@ -179,9 +181,11 @@ impl Encoder, BodySize)>> for Codec { &self.config, )?; } + Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; } + Message::Chunk(None) => { self.encoder.encode_eof(dst)?; } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 5b790469f..3f327171d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,13 +8,12 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; -use actix_rt::time::{sleep_until, Instant, Sleep}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder as _, Encoder as _, Framed, FramedParts}; +use actix_rt::time::sleep_until; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; -use log::{error, trace}; use pin_project_lite::pin_project; use crate::{ @@ -29,6 +28,7 @@ use super::{ codec::Codec, decoder::MAX_BUFFER_SIZE, payload::{Payload, PayloadSender, PayloadStatus}, + timer::TimerState, Message, MessageType, }; @@ -38,11 +38,23 @@ const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE = 0b0000_0010; - const SHUTDOWN = 0b0000_0100; - const READ_DISCONNECT = 0b0000_1000; - const WRITE_DISCONNECT = 0b0001_0000; + /// Set when stream is read for first time. + const STARTED = 0b0000_0001; + + /// Set when full request-response cycle has occurred. + const FINISHED = 0b0000_0010; + + /// Set if connection is in keep-alive (inactive) state. + const KEEP_ALIVE = 0b0000_0100; + + /// Set if in shutdown procedure. + const SHUTDOWN = 0b0000_1000; + + /// Set if read-half is disconnected. + const READ_DISCONNECT = 0b0001_0000; + + /// Set if write-half is disconnected. + const WRITE_DISCONNECT = 0b0010_0000; } } @@ -135,6 +147,7 @@ pin_project! { pub(super) flags: Flags, peer_addr: Option, conn_data: Option>, + config: ServiceConfig, error: Option, #[pin] @@ -142,9 +155,9 @@ pin_project! { payload: Option, messages: VecDeque, - ka_expire: Instant, - #[pin] - ka_timer: Option, + head_timer: TimerState, + ka_timer: TimerState, + shutdown_timer: TimerState, pub(super) io: Option, read_buf: BytesMut, @@ -165,7 +178,6 @@ pin_project! { where S: Service, X: Service, - B: MessageBody, { None, @@ -179,16 +191,40 @@ pin_project! { impl State where S: Service, - X: Service, - B: MessageBody, { - fn is_empty(&self) -> bool { + fn is_none(&self) -> bool { matches!(self, State::None) } } +impl fmt::Debug for State +where + S: Service, + X: Service, + B: MessageBody, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "State::None"), + Self::ExpectCall { .. } => { + f.debug_struct("State::ExpectCall").finish_non_exhaustive() + } + Self::ServiceCall { .. } => { + f.debug_struct("State::ServiceCall").finish_non_exhaustive() + } + Self::SendPayload { .. } => { + f.debug_struct("State::SendPayload").finish_non_exhaustive() + } + Self::SendErrorPayload { .. } => f + .debug_struct("State::SendErrorPayload") + .finish_non_exhaustive(), + } + } +} + +#[derive(Debug)] enum PollResponse { Upgrade(Request), DoNothing, @@ -219,33 +255,25 @@ where peer_addr: Option, conn_data: OnConnectData, ) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE - } else { - Flags::empty() - }; - - // keep-alive timer - let (ka_expire, ka_timer) = match config.keep_alive_timer() { - Some(delay) => (delay.deadline(), Some(delay)), - None => (config.now(), None), - }; - Dispatcher { inner: DispatcherState::Normal { inner: InnerDispatcher { flow, - flags, + flags: Flags::empty(), peer_addr, conn_data: conn_data.0.map(Rc::new), + config: config.clone(), error: None, state: State::None, payload: None, messages: VecDeque::new(), - ka_expire, - ka_timer, + head_timer: TimerState::new(config.client_request_deadline().is_some()), + ka_timer: TimerState::new(config.keep_alive().enabled()), + shutdown_timer: TimerState::new( + config.client_disconnect_deadline().is_some(), + ), io: Some(io), read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), @@ -286,11 +314,12 @@ where } } - // if checked is set to true, delay disconnect until all tasks have finished. fn client_disconnected(self: Pin<&mut Self>) { 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)); } @@ -306,9 +335,12 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))) + log::error!("write zero; closing"); + return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))); } + Poll::Ready(n) => written += n, + Poll::Pending => { write_buf.advance(written); return Poll::Pending; @@ -316,59 +348,70 @@ where } } - // everything has written to io. clear buffer. + // everything has written to I/O; clear buffer write_buf.clear(); - // flush the io and check if get blocked. + // flush the I/O and check if get blocked io.poll_flush(cx) } fn send_response_inner( self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: &impl MessageBody, ) -> Result { - let size = body.size(); let this = self.project(); + + let size = body.size(); + this.codec - .encode(Message::Item((message, size)), this.write_buf) + .encode(Message::Item((res, size)), this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); } + DispatchError::Io(err) })?; - this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); + this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); Ok(size) } fn send_response( mut self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: B, ) -> Result<(), DispatchError> { - let size = self.as_mut().send_response_inner(message, &body)?; - let state = match size { - BodySize::None | BodySize::Sized(0) => State::None, + let size = self.as_mut().send_response_inner(res, &body)?; + let mut this = self.project(); + this.state.set(match size { + BodySize::None | BodySize::Sized(0) => { + this.flags.insert(Flags::FINISHED); + State::None + } _ => State::SendPayload { body }, - }; - self.project().state.set(state); + }); + Ok(()) } fn send_error_response( mut self: Pin<&mut Self>, - message: Response<()>, + res: Response<()>, body: BoxBody, ) -> Result<(), DispatchError> { - let size = self.as_mut().send_response_inner(message, &body)?; - let state = match size { - BodySize::None | BodySize::Sized(0) => State::None, + let size = self.as_mut().send_response_inner(res, &body)?; + let mut this = self.project(); + this.state.set(match size { + BodySize::None | BodySize::Sized(0) => { + this.flags.insert(Flags::FINISHED); + State::None + } _ => State::SendErrorPayload { body }, - }; - self.project().state.set(state); + }); + Ok(()) } @@ -385,63 +428,66 @@ where 'res: loop { let mut this = self.as_mut().project(); match this.state.as_mut().project() { - // no future is in InnerDispatcher state. pop next message. + // no future is in InnerDispatcher state; pop next message StateProj::None => match this.messages.pop_front() { - // handle request message. + // handle request message Some(DispatcherMessage::Item(req)) => { // Handle `EXPECT: 100-Continue` header if req.head().expect() { - // set InnerDispatcher state and continue loop to poll it. + // set InnerDispatcher state and continue loop to poll it let fut = this.flow.expect.call(req); this.state.set(State::ExpectCall { fut }); } else { - // the same as expect call. + // set InnerDispatcher state and continue loop to poll it let fut = this.flow.service.call(req); this.state.set(State::ServiceCall { fut }); }; } - // handle error message. + // 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. + // send_response would update InnerDispatcher state to SendPayload or None + // (If response body is empty) + // continue loop to poll it self.as_mut().send_error_response(res, BoxBody::new(()))?; } - // return with upgrade request and poll it exclusively. + // return with upgrade request and poll it exclusively Some(DispatcherMessage::Upgrade(req)) => { - return Ok(PollResponse::Upgrade(req)); + return Ok(PollResponse::Upgrade(req)) } - // all messages are dealt with. + // 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)?; - } - // send service call error as response - Poll::Ready(Err(err)) => { - let res: Response = err.into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_error_response(res, body)?; - } - - // 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); + 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)?; + } + + // send service call error as response + Poll::Ready(Err(err)) => { + let res: Response = err.into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_error_response(res, body)?; + } + + // 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 fed + // nothing to do except waiting for new incoming data from client + if !self.as_mut().poll_request(cx)? { + return Ok(PollResponse::DoNothing); + } + // else loop } - // otherwise keep loop. } - }, + } StateProj::SendPayload { mut body } => { // keep populate writer buffer until buffer size limit hit, @@ -455,21 +501,26 @@ where Poll::Ready(None) => { this.codec.encode(Message::Chunk(None), this.write_buf)?; + // payload stream finished. // set state to None and handle next message this.state.set(State::None); + this.flags.insert(Flags::FINISHED); + continue 'res; } Poll::Ready(Some(Err(err))) => { - return Err(DispatchError::Body(err.into())) + this.flags.insert(Flags::FINISHED); + return Err(DispatchError::Body(err.into())); } Poll::Pending => return Ok(PollResponse::DoNothing), } } - // buffer is beyond max size. - // return and try to write the whole buffer to io stream. + + // buffer is beyond max size + // return and try to write the whole buffer to I/O stream. return Ok(PollResponse::DrainWriteBuf); } @@ -487,46 +538,55 @@ where Poll::Ready(None) => { this.codec.encode(Message::Chunk(None), this.write_buf)?; - // payload stream finished. + + // payload stream finished // set state to None and handle next message this.state.set(State::None); + this.flags.insert(Flags::FINISHED); + continue 'res; } Poll::Ready(Some(Err(err))) => { + this.flags.insert(Flags::FINISHED); return Err(DispatchError::Body( Error::new_body().with_cause(err).into(), - )) + )); } Poll::Pending => return Ok(PollResponse::DoNothing), } } - // buffer is beyond max size. - // return and try to write the whole buffer to io stream. + + // buffer is beyond max size + // return and try to write the whole buffer to stream return Ok(PollResponse::DrainWriteBuf); } - 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 }); - } + StateProj::ExpectCall { fut } => { + log::trace!(" calling expect service"); - // send expect error as response - Poll::Ready(Err(err)) => { - let res: Response = err.into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_error_response(res, body)?; - } + 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 }); + } - // expect must be solved before progress can be made. - Poll::Pending => return Ok(PollResponse::DoNothing), - }, + // send expect error as response + Poll::Ready(Err(err)) => { + let res: Response = err.into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_error_response(res, body)?; + } + + // expect must be solved before progress can be made. + Poll::Pending => return Ok(PollResponse::DoNothing), + } + } } } } @@ -536,64 +596,76 @@ where req: Request, cx: &mut Context<'_>, ) -> Result<(), DispatchError> { - // Handle `EXPECT: 100-Continue` header - let mut this = self.as_mut().project(); - if req.head().expect() { - // set dispatcher state so the future is pinned. - let fut = this.flow.expect.call(req); - this.state.set(State::ExpectCall { fut }); - } else { - // the same as above. - let fut = this.flow.service.call(req); - this.state.set(State::ServiceCall { fut }); + // initialize dispatcher state + { + let mut this = self.as_mut().project(); + + // Handle `EXPECT: 100-Continue` header + if req.head().expect() { + // set dispatcher state to call expect handler + let fut = this.flow.expect.call(req); + this.state.set(State::ExpectCall { fut }); + } else { + // set dispatcher state to call service handler + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall { fut }); + }; }; - // eagerly poll the future for once(or twice if expect is resolved immediately). + // eagerly poll the future 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. + // 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 fut = this.flow.service.call(req); this.state.set(State::ServiceCall { fut }); + 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. + + // 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 continued Poll::Ready(Err(err)) => { let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_error_response(res, body); } + + // future is pending; return Ok(()) to notify that a new state is + // set and the outer loop should be continue. + Poll::Pending => return Ok(()), } } + 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 + // 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. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); - self.send_response(res, body) + self.as_mut().send_response(res, body) } - // see the comment on ExpectCall state branch's Pending. + + // see the comment on ExpectCall state branch's Pending Poll::Pending => Ok(()), - // see the comment on ExpectCall state branch's Ready(Err(err)). + + // see the comment on ExpectCall state branch's Ready(Err(_)) Poll::Ready(Err(err)) => { let res: Response = err.into(); let (res, body) = res.replace_body(()); - self.send_error_response(res, body) + self.as_mut().send_error_response(res, body) } }; } + _ => { unreachable!( "State must be set to ServiceCall or ExceptCall in handle_request" @@ -604,72 +676,77 @@ where } /// Process one incoming request. + /// + /// Returns true if any meaningful work was done. fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { + let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES; + let can_not_read = !self.can_read(cx); + // limit amount of non-processed requests - if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { + if pipeline_queue_full || can_not_read { return Ok(false); } - let mut updated = false; let mut this = self.as_mut().project(); + + let mut updated = false; + loop { match this.codec.decode(this.read_buf) { Ok(Some(msg)) => { updated = true; - this.flags.insert(Flags::STARTED); match msg { Message::Item(mut req) => { + // head timer only applies to first request on connection + this.head_timer.clear(line!()); + req.head_mut().peer_addr = *this.peer_addr; req.conn_data = this.conn_data.as_ref().map(Rc::clone); match this.codec.message_type() { - // Request is upgradable. add upgrade message and break. - // everything remain in read buffer would be handed to + // request has no payload + MessageType::None => {} + + // Request is upgradable. Add upgrade message and break. + // Everything remaining in read buffer will be handed to // upgraded Request. MessageType::Stream if this.flow.upgrade.is_some() => { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } - // Request is not upgradable. + // request is not upgradable MessageType::Payload | MessageType::Stream => { - /* - PayloadSender and Payload are smart pointers share the - same state. - PayloadSender is attached to dispatcher and used to sink - new chunked request data to state. - Payload is attached to Request and passed to Service::call - where the state can be collected and consumed. - */ + // PayloadSender and Payload are smart pointers share the + // same state. PayloadSender is attached to dispatcher and used + // to sink new chunked request data to state. Payload is + // attached to Request and passed to Service::call where the + // state can be collected and consumed. let (sender, payload) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1 { payload }); - req = req1; + *req.payload() = crate::Payload::H1 { payload }; *this.payload = Some(sender); } - - // Request has no payload. - MessageType::None => {} } // handle request early when no future in InnerDispatcher state. - if this.state.is_empty() { + if this.state.is_none() { self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); } else { this.messages.push_back(DispatcherMessage::Item(req)); } } + Message::Chunk(Some(chunk)) => { if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - error!("Internal server error: unexpected payload chunk"); + log::error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -678,11 +755,12 @@ where break; } } + Message::Chunk(None) => { if let Some(mut payload) = this.payload.take() { payload.feed_eof(); } else { - error!("Internal server error: unexpected eof"); + log::error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -693,38 +771,51 @@ where } } } - // decode is partial and buffer is not full yet. - // break and wait for more read. + + // decode is partial and buffer is not full yet + // break and wait for more read Ok(None) => break, + Err(ParseError::Io(err)) => { + log::trace!("I/O error: {}", &err); self.as_mut().client_disconnected(); this = self.as_mut().project(); *this.error = Some(DispatchError::Io(err)); break; } + Err(ParseError::TooLarge) => { + log::trace!("request head was too big; returning 431 response"); + if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); } - // Requests overflow buffer size should be responded with 431 + + // request heads that overflow buffer size return a 431 error this.messages .push_back(DispatcherMessage::Error(Response::with_body( StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, (), ))); + this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); + break; } + Err(err) => { + log::trace!("parse error {}", &err); + if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); } - // Malformed requests should be responded with 400 + // malformed requests should be responded with 400 this.messages.push_back(DispatcherMessage::Error( Response::bad_request().drop_body(), )); + this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(err.into()); break; @@ -732,92 +823,115 @@ where } } - if updated && this.ka_timer.is_some() { - if let Some(expire) = this.codec.config().keep_alive_expire() { - *this.ka_expire = expire; - } - } Ok(updated) } - /// keep-alive timer - fn poll_keepalive( + fn poll_head_timer( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result<(), DispatchError> { - let mut this = self.as_mut().project(); + let this = self.as_mut().project(); - // 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) { - 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); - } - } - } - 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); + if let TimerState::Active { timer } = this.head_timer { + if timer.as_mut().poll(cx).is_ready() { + // timeout on first request (slow request) return 408 - // 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 { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - let _ = self.as_mut().send_error_response( - Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), - BoxBody::new(()), - ); - this = self.project(); - this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - } - // 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); - } - } + log::trace!( + "timed out on slow request; \ + replying with 408 and closing connection" + ); + + let _ = self.as_mut().send_error_response( + Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), + BoxBody::new(()), + ); + + self.project().flags.insert(Flags::SHUTDOWN); } - } + }; + Ok(()) } - /// Returns true when io stream can be disconnected after write to it. + fn poll_ka_timer( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + let this = self.as_mut().project(); + if let TimerState::Active { timer } = this.ka_timer { + debug_assert!( + this.flags.contains(Flags::KEEP_ALIVE), + "keep-alive flag should be set when timer is active", + ); + debug_assert!( + this.state.is_none(), + "dispatcher should not be in keep-alive phase if state is not none: {:?}", + this.state, + ); + debug_assert!( + this.write_buf.is_empty(), + "dispatcher should not be in keep-alive phase if write_buf is not empty", + ); + + // keep-alive timer has timed out + if timer.as_mut().poll(cx).is_ready() { + // no tasks at hand + log::trace!("timer timed out; closing connection"); + this.flags.insert(Flags::SHUTDOWN); + + if let Some(deadline) = this.config.client_disconnect_deadline() { + // start shutdown timeout if enabled + this.shutdown_timer + .set_and_init(cx, sleep_until(deadline.into()), line!()); + } else { + // no shutdown timeout, drop socket + this.flags.insert(Flags::WRITE_DISCONNECT); + } + } + } + + Ok(()) + } + + fn poll_shutdown_timer( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + let this = self.as_mut().project(); + if let TimerState::Active { timer } = this.shutdown_timer { + debug_assert!( + this.flags.contains(Flags::SHUTDOWN), + "shutdown flag should be set when timer is active", + ); + + // timed-out during shutdown; drop connection + if timer.as_mut().poll(cx).is_ready() { + log::trace!("timed-out during shutdown"); + return Err(DispatchError::DisconnectTimeout); + } + } + + Ok(()) + } + + /// Poll head, keep-alive, and disconnect timer. + fn poll_timers( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result<(), DispatchError> { + self.as_mut().poll_head_timer(cx)?; + self.as_mut().poll_ka_timer(cx)?; + self.as_mut().poll_shutdown_timer(cx)?; + + Ok(()) + } + + /// Returns true when I/O stream can be disconnected after write to it. /// /// It covers these conditions: - /// - `std::io::ErrorKind::ConnectionReset` after partial read. + /// - `std::io::ErrorKind::ConnectionReset` after partial read; /// - all data read done. - #[inline(always)] + #[inline(always)] // TODO: bench this inline fn read_available( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -846,13 +960,12 @@ where // When read_buf is beyond max buffer size the early return could be successfully // be parsed as a new Request. This case would not generate ParseError::TooLarge and // at this point IO stream is not fully read to Pending and would result in - // dispatcher stuck until timeout (KA) + // dispatcher stuck until timeout (keep-alive). // // Note: // This is a perf choice to reduce branch on ::decode. // - // A Request head too large to parse is only checked on - // `httparse::Status::Partial` condition. + // A Request head too large to parse is only checked on `httparse::Status::Partial`. if this.payload.is_none() { // When dispatcher has a payload the responsibility of wake up it would be shift @@ -881,18 +994,29 @@ where match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { Poll::Ready(Ok(n)) => { + this.flags.remove(Flags::FINISHED); + if n == 0 { return Ok(true); } + read_some = true; } - Poll::Pending => return Ok(false), + + Poll::Pending => { + return Ok(false); + } + Poll::Ready(Err(err)) => { return match err.kind() { + // convert WouldBlock error to the same as Pending return io::ErrorKind::WouldBlock => Ok(false), + + // connection reset after partial read io::ErrorKind::ConnectionReset if read_some => Ok(true), + _ => Err(DispatchError::Io(err)), - } + }; } } } @@ -940,27 +1064,60 @@ where } match this.inner.project() { - DispatcherStateProj::Normal { mut inner } => { - inner.as_mut().poll_keepalive(cx)?; + DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { + log::error!("Upgrade handler error: {}", err); + DispatchError::Upgrade + }), - if inner.flags.contains(Flags::SHUTDOWN) { + DispatcherStateProj::Normal { mut inner } => { + log::trace!("start flags: {:?}", &inner.flags); + + trace_timer_states( + "start", + &inner.head_timer, + &inner.ka_timer, + &inner.shutdown_timer, + ); + + inner.as_mut().poll_timers(cx)?; + + let poll = if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer and wait on blocked. + // flush buffer and wait on blocked ready!(inner.as_mut().poll_flush(cx))?; - Pin::new(inner.project().io.as_mut().unwrap()) + Pin::new(inner.as_mut().project().io.as_mut().unwrap()) .poll_shutdown(cx) .map_err(DispatchError::from) } } else { - // read from io stream and fill read buffer. + // read from I/O stream and fill read buffer let should_disconnect = inner.as_mut().read_available(cx)?; + // after reading something from stream, clear keep-alive timer + if !inner.read_buf.is_empty() && inner.flags.contains(Flags::KEEP_ALIVE) { + let inner = inner.as_mut().project(); + inner.flags.remove(Flags::KEEP_ALIVE); + inner.ka_timer.clear(line!()); + } + + if !inner.flags.contains(Flags::STARTED) { + inner.as_mut().project().flags.insert(Flags::STARTED); + + if let Some(deadline) = inner.config.client_request_deadline() { + inner.as_mut().project().head_timer.set_and_init( + cx, + sleep_until(deadline.into()), + line!(), + ); + } + } + inner.as_mut().poll_request(cx)?; - // io stream should to be closed. if should_disconnect { + // I/O stream should to be closed let inner = inner.as_mut().project(); inner.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = inner.payload.take() { @@ -969,11 +1126,27 @@ where }; loop { - // poll_response and populate write buffer. - // drain indicate if write buffer should be emptied before next run. + // poll response to populate write buffer + // drain indicates whether write buffer should be emptied before next run let drain = match inner.as_mut().poll_response(cx)? { PollResponse::DrainWriteBuf => true, - PollResponse::DoNothing => false, + + PollResponse::DoNothing => { + // KEEP_ALIVE is set in send_response_inner if client allows it + // FINISHED is set after writing last chunk of response + if inner.flags.contains(Flags::KEEP_ALIVE | Flags::FINISHED) { + if let Some(timer) = inner.config.keep_alive_deadline() { + inner.as_mut().project().ka_timer.set_and_init( + cx, + sleep_until(timer.into()), + line!(), + ); + } + } + + false + } + // upgrade request and goes Upgrade variant of DispatcherState. PollResponse::Upgrade(req) => { let upgrade = inner.upgrade(req); @@ -985,57 +1158,96 @@ where } }; - // we didn't get WouldBlock from write operation, - // so data get written to kernel completely (macOS) - // and we have to write again otherwise response can get stuck + // we didn't get WouldBlock from write operation, so data get written to + // kernel completely (macOS) and we have to write again otherwise response + // can get stuck // - // TODO: what? is WouldBlock good or bad? - // want to find a reference for this macOS behavior - if inner.as_mut().poll_flush(cx)?.is_pending() || !drain { + // TODO: want to find a reference for this behavior + // see introduced commit: 3872d3ba + let flush_was_ready = inner.as_mut().poll_flush(cx)?.is_ready(); + + // this assert seems to always be true but not willing to commit to it until + // we understand what Nikolay meant when writing the above comment + // debug_assert!(flush_was_ready); + + if !flush_was_ready || !drain { break; } } // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { + log::trace!("client is gone; disconnecting"); return Poll::Ready(Ok(())); } - let is_empty = inner.state.is_empty(); - let inner_p = 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 { + let state_is_none = inner_p.state.is_none(); + + // read half is closed; we do not process any responses + if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none { + log::trace!("read half closed; start shutdown"); inner_p.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors - if is_empty && inner_p.write_buf.is_empty() { + if state_is_none && inner_p.write_buf.is_empty() { if let Some(err) = inner_p.error.take() { - Poll::Ready(Err(err)) + log::error!("stream error: {}", &err); + return 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) + if inner_p.flags.contains(Flags::FINISHED) + && !inner_p.flags.contains(Flags::KEEP_ALIVE) { + inner_p.flags.remove(Flags::FINISHED); inner_p.flags.insert(Flags::SHUTDOWN); - self.poll(cx) + return self.poll(cx); } + // disconnect if shutdown - else if inner_p.flags.contains(Flags::SHUTDOWN) { - self.poll(cx) - } else { - Poll::Pending + if inner_p.flags.contains(Flags::SHUTDOWN) { + return self.poll(cx); } - } else { - Poll::Pending } - } + + trace_timer_states( + "end", + inner_p.head_timer, + inner_p.ka_timer, + inner_p.shutdown_timer, + ); + + Poll::Pending + }; + + log::trace!("end flags: {:?}", &inner.flags); + + poll } - DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { - error!("Upgrade handler error: {}", err); - DispatchError::Upgrade - }), } } } + +#[allow(dead_code)] +fn trace_timer_states( + label: &str, + head_timer: &TimerState, + ka_timer: &TimerState, + shutdown_timer: &TimerState, +) { + log::trace!("{} timers:", label); + + if head_timer.is_enabled() { + log::trace!(" head {}", &head_timer); + } + + if ka_timer.is_enabled() { + log::trace!(" keep-alive {}", &ka_timer); + } + + if shutdown_timer.is_enabled() { + log::trace!(" shutdown {}", &shutdown_timer); + } +} diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 379019c6f..891cce69c 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -17,7 +17,7 @@ use crate::{ h1::{Codec, ExpectHandler, UpgradeHandler}, service::HttpFlow, test::{TestBuffer, TestSeqBuffer}, - Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, + Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, StatusCode, }; fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { @@ -34,7 +34,13 @@ fn stabilize_date_header(payload: &mut [u8]) { } fn ok_service() -> impl Service, Error = Error> { - fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok()))) + status_service(StatusCode::OK) +} + +fn status_service( + status: StatusCode, +) -> impl Service, Error = Error> { + fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status)))) } fn echo_path_service( @@ -65,11 +71,15 @@ fn echo_payload_service() -> impl Service, E #[actix_rt::test] async fn late_request() { - let _ = env_logger::try_init(); - let mut buf = TestBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(ok_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -127,10 +137,16 @@ async fn late_request() { } #[actix_rt::test] -async fn test_basic() { +async fn oneshot_connection() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -179,10 +195,16 @@ async fn test_basic() { } #[actix_rt::test] -async fn test_keep_alive_timeout() { +async fn keep_alive_timeout() { let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Timeout(1), 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Timeout(Duration::from_millis(200)), + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -229,7 +251,7 @@ async fn test_keep_alive_timeout() { .await; // sleep slightly longer than keep-alive timeout - sleep(Duration::from_millis(1100)).await; + sleep(Duration::from_millis(250)).await; lazy(|cx| { assert!( @@ -252,10 +274,16 @@ async fn test_keep_alive_timeout() { } #[actix_rt::test] -async fn test_keep_alive_follow_up_req() { +async fn keep_alive_follow_up_req() { let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n"); - let cfg = ServiceConfig::new(KeepAlive::Timeout(2), 100, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Timeout(Duration::from_millis(500)), + Duration::from_millis(100), + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -302,7 +330,7 @@ async fn test_keep_alive_follow_up_req() { .await; // sleep for less than KA timeout - sleep(Duration::from_millis(200)).await; + sleep(Duration::from_millis(100)).await; lazy(|cx| { assert!( @@ -371,7 +399,7 @@ async fn test_keep_alive_follow_up_req() { } #[actix_rt::test] -async fn test_req_parse_err() { +async fn req_parse_err() { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); @@ -413,7 +441,13 @@ async fn pipelining_ok_then_ok() { ", ); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(1), + Duration::from_millis(1), + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -477,7 +511,13 @@ async fn pipelining_ok_then_bad() { ", ); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::from_millis(1), + Duration::from_millis(1), + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -531,10 +571,16 @@ async fn pipelining_ok_then_bad() { } #[actix_rt::test] -async fn test_expect() { +async fn expect_handling() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); @@ -562,7 +608,6 @@ async fn test_expect() { // polls: manual assert_eq!(h1.poll_count, 1); - eprintln!("poll count: {}", h1.poll_count); if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); @@ -603,10 +648,16 @@ async fn test_expect() { } #[actix_rt::test] -async fn test_eager_expect() { +async fn expect_eager() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); @@ -663,7 +714,7 @@ async fn test_eager_expect() { } #[actix_rt::test] -async fn test_upgrade() { +async fn upgrade_handling() { struct TestUpgrade; impl Service<(Request, Framed)> for TestUpgrade { @@ -683,7 +734,13 @@ async fn test_upgrade() { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); - let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let cfg = ServiceConfig::new( + KeepAlive::Disabled, + Duration::ZERO, + Duration::ZERO, + false, + None, + ); let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade)); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 5fcb2f688..a24ba5911 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -212,7 +212,7 @@ pub(crate) trait MessageType: Sized { // optimized date header, set_date writes \r\n if !has_date { - config.set_date(dst, camel_case); + config.write_date_header(dst, camel_case); } else { // msg eof dst.extend_from_slice(b"\r\n"); @@ -318,16 +318,17 @@ impl MessageType for RequestHeadType { } impl MessageEncoder { - /// Encode message + /// Encode chunk. pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { self.te.encode(msg, buf) } - /// Encode eof + /// Encode EOF. pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { self.te.encode_eof(buf) } + /// Encode message. pub fn encode( &mut self, dst: &mut BytesMut, diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 8c569165d..858cf542a 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -13,6 +13,7 @@ mod encoder; mod expect; mod payload; mod service; +mod timer; mod upgrade; mod utils; @@ -28,9 +29,10 @@ pub use self::utils::SendResponse; #[derive(Debug)] /// Codec message pub enum Message { - /// Http message + /// HTTP message. Item(T), - /// Payload chunk + + /// Payload chunk. Chunk(Option), } diff --git a/actix-http/src/h1/timer.rs b/actix-http/src/h1/timer.rs new file mode 100644 index 000000000..bb69fdb80 --- /dev/null +++ b/actix-http/src/h1/timer.rs @@ -0,0 +1,80 @@ +use std::{fmt, future::Future, pin::Pin, task::Context}; + +use actix_rt::time::{Instant, Sleep}; + +#[derive(Debug)] +pub(super) enum TimerState { + Disabled, + Inactive, + Active { timer: Pin> }, +} + +impl TimerState { + pub(super) fn new(enabled: bool) -> Self { + if enabled { + Self::Inactive + } else { + Self::Disabled + } + } + + pub(super) fn is_enabled(&self) -> bool { + matches!(self, Self::Active { .. } | Self::Inactive) + } + + pub(super) fn set(&mut self, timer: Sleep, line: u32) { + if matches!(self, Self::Disabled) { + log::trace!("setting disabled timer from line {}", line); + } + + *self = Self::Active { + timer: Box::pin(timer), + }; + } + + pub(super) fn set_and_init(&mut self, cx: &mut Context<'_>, timer: Sleep, line: u32) { + self.set(timer, line); + self.init(cx); + } + + pub(super) fn clear(&mut self, line: u32) { + if matches!(self, Self::Disabled) { + log::trace!("trying to clear a disabled timer from line {}", line); + } + + if matches!(self, Self::Inactive) { + log::trace!("trying to clear an inactive timer from line {}", line); + } + + *self = Self::Inactive; + } + + pub(super) fn init(&mut self, cx: &mut Context<'_>) { + if let TimerState::Active { timer } = self { + let _ = timer.as_mut().poll(cx); + } + } +} + +impl fmt::Display for TimerState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TimerState::Disabled => f.write_str("timer is disabled"), + TimerState::Inactive => f.write_str("timer is inactive"), + TimerState::Active { timer } => { + let deadline = timer.deadline(); + let now = Instant::now(); + + if deadline < now { + f.write_str("timer is active and has reached deadline") + } else { + write!( + f, + "timer is active and due to expire in {} milliseconds", + ((deadline - now).as_secs_f32() * 1000.0) + ) + } + } + } + } +} diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index a90eb3466..7a11f9b33 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -57,11 +57,11 @@ where conn_data: OnConnectData, timer: Option>>, ) -> Self { - let ping_pong = config.keep_alive().map(|dur| H2PingPong { + let ping_pong = config.keep_alive().duration().map(|dur| H2PingPong { timer: timer .map(|mut timer| { - // reset timer if it's received from new function. - timer.as_mut().reset(config.now() + dur); + // reuse timer slot if it was initialized for handshake + timer.as_mut().reset((config.now() + dur).into()); timer }) .unwrap_or_else(|| Box::pin(sleep(dur))), @@ -160,8 +160,8 @@ where Poll::Ready(_) => { ping_pong.on_flight = false; - let dead_line = this.config.keep_alive_expire().unwrap(); - ping_pong.timer.as_mut().reset(dead_line); + let dead_line = this.config.keep_alive_deadline().unwrap(); + ping_pong.timer.as_mut().reset(dead_line.into()); } Poll::Pending => { return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) @@ -174,8 +174,8 @@ where ping_pong.ping_pong.send_ping(Ping::opaque())?; - let dead_line = this.config.keep_alive_expire().unwrap(); - ping_pong.timer.as_mut().reset(dead_line); + let dead_line = this.config.keep_alive_deadline().unwrap(); + ping_pong.timer.as_mut().reset(dead_line.into()); ping_pong.on_flight = true; } @@ -322,7 +322,7 @@ fn prepare_response( // set date header if !has_date { let mut bytes = BytesMut::with_capacity(29); - config.set_date_header(&mut bytes); + config.write_date_header_value(&mut bytes); res.headers_mut().insert( DATE, // SAFETY: serialized date-times are known ASCII strings diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index 47d51b420..c8aaaaa5f 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -7,7 +7,7 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::Sleep; +use actix_rt::time::{sleep_until, Sleep}; use bytes::Bytes; use futures_core::{ready, Stream}; use h2::{ @@ -15,17 +15,17 @@ use h2::{ RecvStream, }; +use crate::{ + config::ServiceConfig, + error::{DispatchError, PayloadError}, +}; + mod dispatcher; mod service; pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; -use crate::{ - config::ServiceConfig, - error::{DispatchError, PayloadError}, -}; - /// HTTP/2 peer stream. pub struct Payload { stream: RecvStream, @@ -67,7 +67,9 @@ where { HandshakeWithTimeout { handshake: handshake(io), - timer: config.client_timer().map(Box::pin), + timer: config + .client_request_deadline() + .map(|deadline| Box::pin(sleep_until(deadline.into()))), } } @@ -86,7 +88,7 @@ where let this = self.get_mut(); match Pin::new(&mut this.handshake).poll(cx)? { - // return the timer on success handshake. It can be re-used for h2 ping-pong. + // return the timer on success handshake; its slot can be re-used for h2 ping-pong Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))), Poll::Pending => match this.timer.as_mut() { Some(timer) => { diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 33fb262c4..8f6d1cead 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -630,7 +630,7 @@ impl Removed { /// Returns true if iterator contains no elements, without consuming it. /// /// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate - /// wether any items were actually replaced or removed, respectively. + /// whether any items were actually replaced or removed, respectively. pub fn is_empty(&self) -> bool { match self.inner { // size hint lower bound of smallvec is the correct length diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 473d6cad0..21ed49f0c 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -4,8 +4,7 @@ use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use crate::{ - config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, - helpers::MutWriter, + date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter, }; /// A timestamp with HTTP-style formatting and parsing. diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs new file mode 100644 index 000000000..27161614d --- /dev/null +++ b/actix-http/src/keep_alive.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +/// Connection keep-alive config. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum KeepAlive { + /// Keep-alive duration. + /// + /// `KeepAlive::Timeout(Duration::ZERO)` is mapped to `KeepAlive::Disabled`. + Timeout(Duration), + + /// Rely on OS to shutdown TCP connection. + /// + /// Some defaults can be very long, check your OS documentation. + Os, + + /// Keep-alive is disabled. + /// + /// Connections will be closed immediately. + Disabled, +} + +impl KeepAlive { + pub(crate) fn enabled(&self) -> bool { + !matches!(self, Self::Disabled) + } + + pub(crate) fn duration(&self) -> Option { + match self { + KeepAlive::Timeout(dur) => Some(*dur), + _ => None, + } + } + + /// Map zero duration to disabled. + pub(crate) fn normalize(self) -> KeepAlive { + match self { + KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled, + ka => ka, + } + } +} + +impl Default for KeepAlive { + fn default() -> Self { + Self::Timeout(Duration::from_secs(5)) + } +} + +impl From for KeepAlive { + fn from(dur: Duration) -> Self { + KeepAlive::Timeout(dur).normalize() + } +} + +impl From> for KeepAlive { + fn from(ka_dur: Option) -> Self { + match ka_dur { + Some(dur) => KeepAlive::from(dur), + None => KeepAlive::Disabled, + } + .normalize() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_impls() { + let test: KeepAlive = Duration::from_secs(1).into(); + assert_eq!(test, KeepAlive::Timeout(Duration::from_secs(1))); + + let test: KeepAlive = Duration::from_secs(0).into(); + assert_eq!(test, KeepAlive::Disabled); + + let test: KeepAlive = Some(Duration::from_secs(0)).into(); + assert_eq!(test, KeepAlive::Disabled); + + let test: KeepAlive = None.into(); + assert_eq!(test, KeepAlive::Disabled); + } +} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index f2b415790..c8c7d55c9 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -33,6 +33,7 @@ pub use ::http::{Method, StatusCode, Version}; pub mod body; mod builder; mod config; +mod date; #[cfg(feature = "__compress")] pub mod encoding; pub mod error; @@ -42,7 +43,10 @@ pub mod h2; pub mod header; mod helpers; mod http_message; +mod keep_alive; mod message; +#[cfg(test)] +mod notify_on_drop; mod payload; mod requests; mod responses; @@ -51,11 +55,12 @@ pub mod test; pub mod ws; pub use self::builder::HttpServiceBuilder; -pub use self::config::{KeepAlive, ServiceConfig}; +pub use self::config::ServiceConfig; pub use self::error::Error; pub use self::extensions::Extensions; pub use self::header::ContentEncoding; pub use self::http_message::HttpMessage; +pub use self::keep_alive::KeepAlive; pub use self::message::ConnectionType; pub use self::message::Message; #[allow(deprecated)] diff --git a/actix-http/src/notify_on_drop.rs b/actix-http/src/notify_on_drop.rs new file mode 100644 index 000000000..98544bb5d --- /dev/null +++ b/actix-http/src/notify_on_drop.rs @@ -0,0 +1,49 @@ +/// Test Module for checking the drop state of certain async tasks that are spawned +/// with `actix_rt::spawn` +/// +/// The target task must explicitly generate `NotifyOnDrop` when spawn the task +use std::cell::RefCell; + +thread_local! { + static NOTIFY_DROPPED: RefCell> = RefCell::new(None); +} + +/// Check if the spawned task is dropped. +/// +/// # Panics +/// Panics when there was no `NotifyOnDrop` instance on current thread. +pub(crate) fn is_dropped() -> bool { + NOTIFY_DROPPED.with(|bool| { + bool.borrow() + .expect("No NotifyOnDrop existed on current thread") + }) +} + +pub(crate) struct NotifyOnDrop; + +impl NotifyOnDrop { + /// # Panics + /// Panics hen construct multiple instances on any given thread. + pub(crate) fn new() -> Self { + NOTIFY_DROPPED.with(|bool| { + let mut bool = bool.borrow_mut(); + if bool.is_some() { + panic!("NotifyOnDrop existed on current thread"); + } else { + *bool = Some(false); + } + }); + + NotifyOnDrop + } +} + +impl Drop for NotifyOnDrop { + fn drop(&mut self) { + NOTIFY_DROPPED.with(|bool| { + if let Some(b) = bool.borrow_mut().as_mut() { + *b = true; + } + }); + } +} diff --git a/actix-http/src/requests/head.rs b/actix-http/src/requests/head.rs index 06fd0429e..4558801f3 100644 --- a/actix-http/src/requests/head.rs +++ b/actix-http/src/requests/head.rs @@ -130,8 +130,8 @@ impl RequestHead { } } + /// Request contains `EXPECT` header. #[inline] - /// Request contains `EXPECT` header pub fn expect(&self) -> bool { self.flags.contains(Flags::EXPECT) } diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index 870073ab3..cb47c4b7a 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -42,7 +42,7 @@ impl ResponseHead { &mut self.headers } - /// Sets the flag that controls wether to send headers formatted as Camel-Case. + /// Sets the flag that controls whether to send headers formatted as Camel-Case. /// /// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase. #[inline] @@ -210,14 +210,15 @@ mod tests { use memchr::memmem; use crate::{ + h1::H1Service, header::{HeaderName, HeaderValue}, - Error, HttpService, Request, Response, + Error, Request, Response, ServiceConfig, }; #[actix_rt::test] async fn camel_case_headers() { let mut srv = actix_http_test::test_server(|| { - HttpService::new(|req: Request| async move { + H1Service::with_config(ServiceConfig::default(), |req: Request| async move { let mut res = Response::ok(); if req.path().contains("camel") { @@ -228,6 +229,7 @@ mod tests { HeaderName::from_static("foo-bar"), HeaderValue::from_static("baz"), ); + Ok::<_, Error>(res) }) .tcp() @@ -235,9 +237,11 @@ mod tests { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); + let _ = stream + .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n") + .unwrap(); + let mut data = vec![]; + let _ = stream.read_to_end(&mut data).unwrap(); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert!(memmem::find(&data, b"Foo-Bar").is_some()); assert!(memmem::find(&data, b"foo-bar").is_none()); @@ -247,9 +251,11 @@ mod tests { assert!(memmem::find(&data, b"content-length").is_none()); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); + let _ = stream + .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n") + .unwrap(); + let mut data = vec![]; + let _ = stream.read_to_end(&mut data).unwrap(); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert!(memmem::find(&data, b"Foo-Bar").is_none()); assert!(memmem::find(&data, b"foo-bar").is_some()); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index cd2efe678..4fe573aa5 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -19,9 +19,8 @@ use pin_project_lite::pin_project; use crate::{ body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, - config::{KeepAlive, ServiceConfig}, error::DispatchError, - h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, + h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. @@ -43,9 +42,9 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create builder for `HttpService` instance. + /// Constructs builder for `HttpService` instance. pub fn build() -> HttpServiceBuilder { - HttpServiceBuilder::new() + HttpServiceBuilder::default() } } @@ -58,12 +57,10 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create new `HttpService` instance. + /// Constructs new `HttpService` instance from service with default config. pub fn new>(service: F) -> Self { - let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None); - HttpService { - cfg, + cfg: ServiceConfig::default(), srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, @@ -72,7 +69,7 @@ where } } - /// Create new `HttpService` instance with config. + /// Constructs new `HttpService` instance from config and service. pub(crate) fn with_config>( cfg: ServiceConfig, service: F, @@ -97,11 +94,10 @@ where >::Future: 'static, B: MessageBody, { - /// Provide service for `EXPECT: 100-Continue` support. + /// Sets service for `Expect: 100-Continue` handling. /// - /// Service get called with request that contains `EXPECT` header. - /// Service must return request in case of success, in that case - /// request will be forwarded to main service. + /// An expect service is called with requests that contain an `Expect` header. A successful + /// response type is also a request which will be forwarded to the main service. pub fn expect(self, expect: X1) -> HttpService where X1: ServiceFactory, @@ -118,10 +114,10 @@ where } } - /// Provide service for custom `Connection: UPGRADE` support. + /// Sets service for custom `Connection: Upgrade` handling. /// - /// If service is provided then normal requests handling get halted - /// and this service get called with original request and framed object. + /// If service is provided then normal requests handling get halted and this service get called + /// with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 0d4d342ec..6212c19d1 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -242,7 +242,7 @@ impl io::Read for TestBuffer { impl io::Write for TestBuffer { fn write(&mut self, buf: &[u8]) -> io::Result { - RefCell::borrow_mut(&self.write_buf).extend(buf); + self.write_buf.borrow_mut().extend(buf); Ok(buf.len()) } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index a3adcdfd6..5888527f1 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -31,7 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h1_v2() { +async fn h1_v2() { let srv = test_server(move || { HttpService::build() .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -59,7 +59,7 @@ async fn test_h1_v2() { } #[actix_rt::test] -async fn test_connection_close() { +async fn connection_close() { let srv = test_server(move || { HttpService::build() .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -73,7 +73,7 @@ async fn test_connection_close() { } #[actix_rt::test] -async fn test_with_query_parameter() { +async fn with_query_parameter() { let srv = test_server(move || { HttpService::build() .finish(|req: Request| async move { @@ -104,7 +104,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h1_expect() { +async fn h1_expect() { let srv = test_server(move || { HttpService::build() .expect(|req: Request| async { diff --git a/actix-http/tests/test_h2_timer.rs b/actix-http/tests/test_h2_timer.rs index 2b9c26e4a..2e1480297 100644 --- a/actix-http/tests/test_h2_timer.rs +++ b/actix-http/tests/test_h2_timer.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, time::Duration}; use actix_http::{error::Error, HttpService, Response}; use actix_server::Server; @@ -19,7 +19,7 @@ async fn h2_ping_pong() -> io::Result<()> { .workers(1) .listen("h2_ping_pong", lst, || { HttpService::build() - .keep_alive(3) + .keep_alive(Duration::from_secs(3)) .h2(|_| async { Ok::<_, Error>(Response::ok()) }) .tcp() })? @@ -92,10 +92,10 @@ async fn h2_handshake_timeout() -> io::Result<()> { .workers(1) .listen("h2_ping_pong", lst, || { HttpService::build() - .keep_alive(30) + .keep_alive(Duration::from_secs(30)) // set first request timeout to 5 seconds. // this is the timeout used for http2 handshake. - .client_timeout(5000) + .client_request_timeout(Duration::from_secs(5)) .h2(|_| async { Ok::<_, Error>(Response::ok()) }) .tcp() })? diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 1bb574fd6..1b5de3425 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,7 +2,7 @@ use std::{ convert::Infallible, io::{Read, Write}, net, thread, - time::Duration, + time::{Duration, Instant}, }; use actix_http::{ @@ -22,12 +22,12 @@ use futures_util::{ use regex::Regex; #[actix_rt::test] -async fn test_h1() { +async fn h1_basic() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .h1(|req: Request| { assert!(req.peer_addr().is_some()); ok::<_, Infallible>(Response::ok()) @@ -43,12 +43,12 @@ async fn test_h1() { } #[actix_rt::test] -async fn test_h1_2() { +async fn h1_2() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) + .client_request_timeout(Duration::from_secs(1)) + .client_disconnect_timeout(Duration::from_secs(1)) .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); @@ -75,7 +75,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_expect_continue() { +async fn expect_continue() { let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { @@ -106,7 +106,7 @@ async fn test_expect_continue() { } #[actix_rt::test] -async fn test_expect_continue_h1() { +async fn expect_continue_h1() { let mut srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { @@ -139,7 +139,7 @@ async fn test_expect_continue_h1() { } #[actix_rt::test] -async fn test_chunked_payload() { +async fn chunked_payload() { let chunk_sizes = vec![32768, 32, 32768]; let total_size: usize = chunk_sizes.iter().sum(); @@ -197,26 +197,43 @@ async fn test_chunked_payload() { } #[actix_rt::test] -async fn test_slow_request() { +async fn slow_request_408() { let mut srv = test_server(|| { HttpService::build() - .client_timeout(100) + .client_request_timeout(Duration::from_millis(200)) + .keep_alive(Duration::from_secs(2)) .finish(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; + let start = Instant::now(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + assert!( + data.starts_with("HTTP/1.1 408 Request Timeout"), + "response was not 408: {}", + data + ); + + let diff = start.elapsed(); + + if diff < Duration::from_secs(1) { + // test success + } else if diff < Duration::from_secs(3) { + panic!("request seems to have wrongly timed-out according to keep-alive"); + } else { + panic!("request took way too long to time out"); + } srv.stop().await; } #[actix_rt::test] -async fn test_http1_malformed_request() { +async fn http1_malformed_request() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -234,7 +251,7 @@ async fn test_http1_malformed_request() { } #[actix_rt::test] -async fn test_http1_keepalive() { +async fn http1_keepalive() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -257,23 +274,25 @@ async fn test_http1_keepalive() { } #[actix_rt::test] -async fn test_http1_keepalive_timeout() { +async fn http1_keepalive_timeout() { let mut srv = test_server(|| { HttpService::build() - .keep_alive(1) + .keep_alive(Duration::from_secs(1)) .h1(|_| ok::<_, Infallible>(Response::ok())) .tcp() }) .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; + + let _ = stream.write_all(b"GET /test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 256]; let _ = stream.read(&mut data); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); - let mut data = vec![0; 1024]; + let mut data = vec![0; 256]; let res = stream.read(&mut data).unwrap(); assert_eq!(res, 0); @@ -281,7 +300,7 @@ async fn test_http1_keepalive_timeout() { } #[actix_rt::test] -async fn test_http1_keepalive_close() { +async fn http1_keepalive_close() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -303,7 +322,7 @@ async fn test_http1_keepalive_close() { } #[actix_rt::test] -async fn test_http10_keepalive_default_close() { +async fn http10_keepalive_default_close() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -325,7 +344,7 @@ async fn test_http10_keepalive_default_close() { } #[actix_rt::test] -async fn test_http10_keepalive() { +async fn http10_keepalive() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok())) @@ -354,7 +373,7 @@ async fn test_http10_keepalive() { } #[actix_rt::test] -async fn test_http1_keepalive_disabled() { +async fn http1_keepalive_disabled() { let mut srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) @@ -377,7 +396,7 @@ async fn test_http1_keepalive_disabled() { } #[actix_rt::test] -async fn test_content_length() { +async fn content_length() { use actix_http::{ header::{HeaderName, HeaderValue}, StatusCode, @@ -426,7 +445,7 @@ async fn test_content_length() { } #[actix_rt::test] -async fn test_h1_headers() { +async fn h1_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -492,7 +511,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h1_body() { +async fn h1_body() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -511,7 +530,7 @@ async fn test_h1_body() { } #[actix_rt::test] -async fn test_h1_head_empty() { +async fn h1_head_empty() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -538,7 +557,7 @@ async fn test_h1_head_empty() { } #[actix_rt::test] -async fn test_h1_head_binary() { +async fn h1_head_binary() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -565,7 +584,7 @@ async fn test_h1_head_binary() { } #[actix_rt::test] -async fn test_h1_head_binary2() { +async fn h1_head_binary2() { let mut srv = test_server(|| { HttpService::build() .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -588,7 +607,7 @@ async fn test_h1_head_binary2() { } #[actix_rt::test] -async fn test_h1_body_length() { +async fn h1_body_length() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -612,7 +631,7 @@ async fn test_h1_body_length() { } #[actix_rt::test] -async fn test_h1_body_chunked_explicit() { +async fn h1_body_chunked_explicit() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -649,7 +668,7 @@ async fn test_h1_body_chunked_explicit() { } #[actix_rt::test] -async fn test_h1_body_chunked_implicit() { +async fn h1_body_chunked_implicit() { let mut srv = test_server(|| { HttpService::build() .h1(|_| { @@ -680,7 +699,7 @@ async fn test_h1_body_chunked_implicit() { } #[actix_rt::test] -async fn test_h1_response_http_error_handling() { +async fn h1_response_http_error_handling() { let mut srv = test_server(|| { HttpService::build() .h1(fn_service(|_| { @@ -719,7 +738,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h1_service_error() { +async fn h1_service_error() { let mut srv = test_server(|| { HttpService::build() .h1(|_| err::, _>(BadRequest)) @@ -738,7 +757,7 @@ async fn test_h1_service_error() { } #[actix_rt::test] -async fn test_h1_on_connect() { +async fn h1_on_connect() { let mut srv = test_server(|| { HttpService::build() .on_connect_ext(|_, data| { @@ -761,7 +780,7 @@ async fn test_h1_on_connect() { /// Tests compliance with 304 Not Modified spec in RFC 7232 §4.1. /// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 #[actix_rt::test] -async fn test_not_modified_spec_h1() { +async fn not_modified_spec_h1() { // TODO: this test needing a few seconds to complete reveals some weirdness with either the // dispatcher or the client, though similar hangs occur on other tests in this file, only // succeeding, it seems, because of the keepalive timer diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index ed8c61fd6..8b3ab8e1b 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -109,7 +109,7 @@ async fn service(msg: Frame) -> Result { } #[actix_rt::test] -async fn test_simple() { +async fn simple() { let mut srv = test_server(|| { HttpService::build() .upgrade(fn_factory(|| async { diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 32ab2344f..3877f4fbf 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611] + +[#2611]: https://github.com/actix/actix-web/pull/2611 ## 0.1.0-beta.11 - 2022-01-04 diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index f86120f2f..d44bc7a45 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -149,7 +149,7 @@ where let local_addr = tcp.local_addr().unwrap(); let factory = factory.clone(); let srv_cfg = cfg.clone(); - let timeout = cfg.client_timeout; + let timeout = cfg.client_request_timeout; let builder = Server::build().workers(1).disable_signals().system_exit(); @@ -167,7 +167,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -183,7 +183,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -199,7 +199,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .tcp() }), @@ -218,7 +218,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -234,7 +234,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -250,7 +250,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .openssl(acceptor.clone()) }), @@ -269,7 +269,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h1(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -285,7 +285,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .h2(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -301,7 +301,7 @@ where .map_err(|err| err.into().error_response()); HttpService::build() - .client_timeout(timeout) + .client_request_timeout(timeout) .finish(map_config(fac, move |_| app_cfg.clone())) .rustls(config.clone()) }), @@ -388,7 +388,7 @@ pub fn config() -> TestServerConfig { pub struct TestServerConfig { tp: HttpVer, stream: StreamType, - client_timeout: u64, + client_request_timeout: Duration, } impl Default for TestServerConfig { @@ -403,7 +403,7 @@ impl TestServerConfig { TestServerConfig { tp: HttpVer::Both, stream: StreamType::Tcp, - client_timeout: 5000, + client_request_timeout: Duration::from_secs(5), } } @@ -433,9 +433,9 @@ impl TestServerConfig { self } - /// Set client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; + /// Set client timeout for first request. + pub fn client_request_timeout(mut self, dur: Duration) -> Self { + self.client_request_timeout = dur; self } } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index cf716db72..4f6a87ac5 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -70,7 +70,7 @@ where let is_expect = if head.as_ref().headers.contains_key(EXPECT) { match body.size() { BodySize::None | BodySize::Sized(0) => { - let keep_alive = framed.codec_ref().keepalive(); + let keep_alive = framed.codec_ref().keep_alive(); framed.io_mut().on_release(keep_alive); // TODO: use a new variant or a new type better describing error violate @@ -119,7 +119,7 @@ where match pin_framed.codec_ref().message_type() { h1::MessageType::None => { - let keep_alive = pin_framed.codec_ref().keepalive(); + let keep_alive = pin_framed.codec_ref().keep_alive(); pin_framed.io_mut().on_release(keep_alive); Ok((head, Payload::None)) @@ -223,7 +223,7 @@ impl Stream for PlStream { match ready!(this.framed.as_mut().next_item(cx)?) { Some(Some(chunk)) => Poll::Ready(Some(Ok(chunk))), Some(None) => { - let keep_alive = this.framed.codec_ref().keepalive(); + let keep_alive = this.framed.codec_ref().keep_alive(); this.framed.io_mut().on_release(keep_alive); Poll::Ready(None) } diff --git a/src/server.rs b/src/server.rs index ed0c965b3..83e025fb0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,6 +4,7 @@ use std::{ marker::PhantomData, net, sync::{Arc, Mutex}, + time::Duration, }; use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response}; @@ -27,8 +28,8 @@ struct Socket { struct Config { host: Option, keep_alive: KeepAlive, - client_timeout: u64, - client_shutdown: u64, + client_request_timeout: Duration, + client_disconnect_timeout: Duration, } /// An HTTP Server. @@ -88,9 +89,9 @@ where factory, config: Arc::new(Mutex::new(Config { host: None, - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_shutdown: 5000, + keep_alive: KeepAlive::default(), + client_request_timeout: Duration::from_secs(5), + client_disconnect_timeout: Duration::from_secs(1), })), backlog: 1024, sockets: Vec::new(), @@ -200,11 +201,17 @@ where /// To disable timeout set value to 0. /// /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(self, val: u64) -> Self { - self.config.lock().unwrap().client_timeout = val; + pub fn client_request_timeout(self, dur: Duration) -> Self { + self.config.lock().unwrap().client_request_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_timeout(self, dur: Duration) -> Self { + self.client_request_timeout(dur) + } + /// Set server connection shutdown timeout in milliseconds. /// /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete @@ -213,11 +220,17 @@ where /// To disable timeout set value to 0. /// /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(self, val: u64) -> Self { - self.config.lock().unwrap().client_shutdown = val; + pub fn client_disconnect_timeout(self, dur: Duration) -> Self { + self.config.lock().unwrap().client_disconnect_timeout = dur; self } + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Renamed to `client_request_timeout`.")] + pub fn client_shutdown(self, dur: u64) -> Self { + self.client_disconnect_timeout(Duration::from_millis(dur)) + } + /// Set server host name. /// /// Host name is used by application router as a hostname for url generation. @@ -291,8 +304,8 @@ where let mut svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .local_addr(addr); if let Some(handler) = on_connect_fn.clone() { @@ -349,8 +362,8 @@ where let svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .local_addr(addr); let svc = if let Some(handler) = on_connect_fn.clone() { @@ -410,8 +423,8 @@ where let svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); let svc = if let Some(handler) = on_connect_fn.clone() { svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) @@ -537,8 +550,8 @@ where fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({ let mut svc = HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); if let Some(handler) = on_connect_fn.clone() { svc = svc @@ -593,8 +606,8 @@ where fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( HttpService::build() .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) .finish(map_config(fac, move |_| config.clone())), ) }, diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 6ea8e520c..86e0575f3 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -26,9 +26,9 @@ async fn test_start() { .backlog(1) .max_connections(10) .max_connection_rate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) + .keep_alive(Duration::from_secs(10)) + .client_request_timeout(Duration::from_secs(5)) + .client_disconnect_timeout(Duration::ZERO) .server_hostname("localhost") .system_exit() .disable_signals() diff --git a/tests/test_server.rs b/tests/test_server.rs index b8193a004..bd8934061 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,6 +8,7 @@ use std::{ io::{Read, Write}, pin::Pin, task::{Context, Poll}, + time::Duration, }; use actix_web::{ @@ -835,9 +836,10 @@ async fn test_server_cookies() { async fn test_slow_request() { use std::net; - let srv = actix_test::start_with(actix_test::config().client_timeout(200), || { - App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))) - }); + let srv = actix_test::start_with( + actix_test::config().client_request_timeout(Duration::from_millis(200)), + || App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), + ); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut data = String::new(); From cd511affd5e7f7c9c2883a82757c5e4e1791e64b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 21:22:23 +0000 Subject: [PATCH 321/861] add ws and http2 feature flags (#2618) --- Cargo.toml | 2 +- actix-http/CHANGES.md | 3 ++ actix-http/Cargo.toml | 47 ++++++++++++++++++---------- actix-http/src/builder.rs | 7 +++-- actix-http/src/config.rs | 1 + actix-http/src/encoding/encoder.rs | 8 ++--- actix-http/src/error.rs | 26 +++++++++++----- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/http_message.rs | 2 +- actix-http/src/keep_alive.rs | 1 + actix-http/src/lib.rs | 5 ++- actix-http/src/payload.rs | 21 +++++++++++-- actix-http/src/service.rs | 50 ++++++++++++++++++++++++++---- actix-http/src/ws/codec.rs | 10 +++--- actix-http/src/ws/dispatcher.rs | 8 +++-- actix-http/src/ws/frame.rs | 8 +++-- awc/Cargo.toml | 2 +- 18 files changed, 148 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99ff85e8d..38c8512bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = "3.0.0-beta.19" +actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } actix-router = "0.5.0-rc.2" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a748bc43f..38bec78ba 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,8 @@ - Implement `From` for `KeepAlive`. [#2611] - Implement `From>` for `KeepAlive`. [#2611] - Implement `Default` for `HttpServiceBuilder`. [#2611] +- Crate `ws` feature flag, disabled by default. [#2618] +- Crate `http2` feature flag, disabled by default. [#2618] ### Changed - Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] @@ -27,6 +29,7 @@ - `HttpServiceBuilder::new`; use `default` instead. [#2611] [#2611]: https://github.com/actix/actix-web/pull/2611 +[#2618]: https://github.com/actix/actix-web/pull/2618 ## 3.0.0-beta.19 - 2022-01-21 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 11bfa7a1a..f68eda074 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,54 +29,69 @@ path = "src/lib.rs" [features] default = [] -# openssl +# HTTP/2 protocol support +http2 = ["h2"] + +# WebSocket protocol implementation +ws = [ + "local-channel", + "base64", + "rand", + "sha-1", +] + +# TLS via OpenSSL openssl = ["actix-tls/accept", "actix-tls/openssl"] -# rustls support +# TLS via Rustls rustls = ["actix-tls/accept", "actix-tls/rustls"] -# enable compression support -compress-brotli = ["brotli", "__compress"] -compress-gzip = ["flate2", "__compress"] -compress-zstd = ["zstd", "__compress"] +# Compression codecs +compress-brotli = ["__compress", "brotli"] +compress-gzip = ["__compress", "flate2"] +compress-zstd = ["__compress", "zstd"] # Internal (PRIVATE!) features used to aid testing and cheking feature status. -# Don't rely on these whatsoever. They may disappear at anytime. +# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime. __compress = [] [dependencies] -actix-service = "2.0.0" +actix-service = "2" actix-codec = "0.4.1" -actix-utils = "3.0.0" +actix-utils = "3" actix-rt = { version = "2.2", default-features = false } ahash = "0.7" -base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -local-channel = "0.1" log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" -rand = "0.8" -sha-1 = "0.10" smallvec = "1.6.1" -# tls +# http2 +h2 = { version = "0.3.9", optional = true } + +# websockets +local-channel = { version = "0.1", optional = true } +base64 = { version = "0.13", optional = true } +rand = { version = "0.8", optional = true } +sha-1 = { version = "0.10", optional = true } + +# openssl/rustls actix-tls = { version = "3.0.0", default-features = false, optional = true } -# compression +# compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 9dd145ce1..526a23d53 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -6,7 +6,6 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ body::{BoxBody, MessageBody}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, - h2::H2Service, service::HttpService, ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig, }; @@ -211,7 +210,8 @@ where } /// Finish service configuration and create a HTTP service for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service + #[cfg(feature = "http2")] + pub fn h2(self, service: F) -> crate::h2::H2Service where F: IntoServiceFactory, S::Error: Into> + 'static, @@ -228,7 +228,8 @@ where self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext) + crate::h2::H2Service::with_config(cfg, service.into_factory()) + .on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index aa05d6aba..8045910be 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -117,6 +117,7 @@ impl ServiceConfig { dst.extend_from_slice(&buf); } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 116fe76ab..2f104ee8f 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -352,7 +352,7 @@ impl ContentEncoder { ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding br encoding: {}", err); + log::trace!("Error decoding br encoding: {}", err); Err(err) } }, @@ -361,7 +361,7 @@ impl ContentEncoder { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding gzip encoding: {}", err); + log::trace!("Error decoding gzip encoding: {}", err); Err(err) } }, @@ -370,7 +370,7 @@ impl ContentEncoder { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding deflate encoding: {}", err); + log::trace!("Error decoding deflate encoding: {}", err); Err(err) } }, @@ -379,7 +379,7 @@ impl ContentEncoder { ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - trace!("Error decoding ztsd encoding: {}", err); + log::trace!("Error decoding ztsd encoding: {}", err); Err(err) } }, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index df6d3813a..841322861 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err use derive_more::{Display, Error, From}; use http::{uri::InvalidUri, StatusCode}; -use crate::{body::BoxBody, ws, Response}; +use crate::{body::BoxBody, Response}; pub use http::Error as HttpError; @@ -61,6 +61,7 @@ impl Error { Self::new(Kind::Encoder) } + #[allow(unused)] // used with `ws` feature flag pub(crate) fn new_ws() -> Self { Self::new(Kind::Ws) } @@ -139,14 +140,16 @@ impl From for Error { } } -impl From for Error { - fn from(err: ws::HandshakeError) -> Self { +#[cfg(feature = "ws")] +impl From for Error { + fn from(err: crate::ws::HandshakeError) -> Self { Self::new_ws().with_cause(err) } } -impl From for Error { - fn from(err: ws::ProtocolError) -> Self { +#[cfg(feature = "ws")] +impl From for Error { + fn from(err: crate::ws::ProtocolError) -> Self { Self::new_ws().with_cause(err) } } @@ -277,8 +280,9 @@ pub enum PayloadError { UnknownLength, /// HTTP/2 payload error. + #[cfg(feature = "http2")] #[display(fmt = "{}", _0)] - Http2Payload(h2::Error), + Http2Payload(::h2::Error), /// Generic I/O error. #[display(fmt = "{}", _0)] @@ -293,14 +297,16 @@ impl std::error::Error for PayloadError { PayloadError::EncodingCorrupted => None, PayloadError::Overflow => None, PayloadError::UnknownLength => None, + #[cfg(feature = "http2")] PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error), PayloadError::Io(err) => Some(err as &dyn std::error::Error), } } } -impl From for PayloadError { - fn from(err: h2::Error) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::Error> for PayloadError { + fn from(err: ::h2::Error) -> Self { PayloadError::Http2Payload(err) } } @@ -356,6 +362,7 @@ pub enum DispatchError { /// HTTP/2 error. #[display(fmt = "{}", _0)] + #[cfg(feature = "http2")] H2(h2::Error), /// The first request did not complete within the specified timeout. @@ -379,7 +386,10 @@ impl StdError for DispatchError { DispatchError::Body(err) => Some(&**err), DispatchError::Io(err) => Some(err), DispatchError::Parse(err) => Some(err), + + #[cfg(feature = "http2")] DispatchError::H2(err) => Some(err), + _ => None, } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7a11f9b33..d528bec96 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -141,7 +141,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => warn!("{:?}", err), + DispatchError::SendData(err) => log::warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 469648054..653982d37 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -355,7 +355,7 @@ where } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 068e23b96..198254e02 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -55,7 +55,7 @@ pub trait HttpMessage: Sized { "" } - /// Get content type encoding + /// Get content type encoding. /// /// UTF-8 is used by default, If request charset is not set. fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs index 27161614d..feb7ff5df 100644 --- a/actix-http/src/keep_alive.rs +++ b/actix-http/src/keep_alive.rs @@ -24,6 +24,7 @@ impl KeepAlive { !matches!(self, Self::Disabled) } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn duration(&self) -> Option { match self { KeepAlive::Timeout(dur) => Some(*dur), diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index c8c7d55c9..dbff89612 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -24,9 +24,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[macro_use] -extern crate log; - pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -39,6 +36,7 @@ pub mod encoding; pub mod error; mod extensions; pub mod h1; +#[cfg(feature = "http2")] pub mod h2; pub mod header; mod helpers; @@ -52,6 +50,7 @@ mod requests; mod responses; mod service; pub mod test; +#[cfg(feature = "ws")] pub mod ws; pub use self::builder::HttpServiceBuilder; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index aed24e963..33d9ec6f5 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -16,6 +16,18 @@ pub type BoxedPayloadStream = Pin { + None, + H1 { payload: crate::h1::Payload }, + Stream { #[pin] payload: S }, + } +} + +#[cfg(feature = "http2")] pin_project! { /// A streaming payload. #[project = PayloadProj] @@ -33,14 +45,16 @@ impl From for Payload { } } +#[cfg(feature = "http2")] impl From for Payload { fn from(payload: crate::h2::Payload) -> Self { Payload::H2 { payload } } } -impl From for Payload { - fn from(stream: h2::RecvStream) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::RecvStream> for Payload { + fn from(stream: ::h2::RecvStream) -> Self { Payload::H2 { payload: crate::h2::Payload::new(stream), } @@ -71,7 +85,10 @@ where match self.project() { PayloadProj::None => Poll::Ready(None), PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), + + #[cfg(feature = "http2")] PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::Stream { payload } => payload.poll_next(cx), } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 4fe573aa5..b220e55a4 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -20,7 +20,7 @@ use crate::{ body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, error::DispatchError, - h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, + h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. @@ -502,10 +502,11 @@ where let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); match proto { + #[cfg(feature = "http2")] Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake { handshake: Some(( - h2::handshake_with_timeout(io, &self.cfg), + crate::h2::handshake_with_timeout(io, &self.cfg), self.cfg.clone(), self.flow.clone(), conn_data, @@ -514,6 +515,11 @@ where }, }, + #[cfg(not(feature = "http2"))] + Protocol::Http2 => { + panic!("HTTP/2 support is disabled (enable with the `http2` feature flag)") + } + Protocol::Http1 => HttpServiceHandlerResponse { state: State::H1 { dispatcher: h1::Dispatcher::new( @@ -531,6 +537,7 @@ where } } +#[cfg(not(feature = "http2"))] pin_project! { #[project = StateProj] enum State @@ -552,10 +559,37 @@ pin_project! { U::Error: fmt::Display, { H1 { #[pin] dispatcher: h1::Dispatcher }, - H2 { #[pin] dispatcher: h2::Dispatcher }, + } +} + +#[cfg(feature = "http2")] +pin_project! { + #[project = StateProj] + enum State + where + T: AsyncRead, + T: AsyncWrite, + T: Unpin, + + S: Service, + S::Future: 'static, + S::Error: Into>, + + B: MessageBody, + + X: Service, + X::Error: Into>, + + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + H1 { #[pin] dispatcher: h1::Dispatcher }, + + H2 { #[pin] dispatcher: crate::h2::Dispatcher }, + H2Handshake { handshake: Option<( - h2::HandshakeWithTimeout, + crate::h2::HandshakeWithTimeout, ServiceConfig, Rc>, OnConnectData, @@ -614,21 +648,25 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project().state.project() { StateProj::H1 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2 { - dispatcher: h2::Dispatcher::new( + dispatcher: crate::h2::Dispatcher::new( conn, flow, config, peer_addr, conn_data, timer, ), }); self.poll(cx) } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index f5b755eec..6e7aa7c11 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -3,9 +3,11 @@ use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; -use super::frame::Parser; -use super::proto::{CloseReason, OpCode}; -use super::ProtocolError; +use super::{ + frame::Parser, + proto::{CloseReason, OpCode}, + ProtocolError, +}; /// A WebSocket message. #[derive(Debug, PartialEq)] @@ -251,7 +253,7 @@ impl Decoder for Codec { } } _ => { - error!("Unfinished fragment {:?}", opcode); + log::error!("Unfinished fragment {:?}", opcode); Err(ProtocolError::ContinuationFragment(opcode)) } }; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index f12ae1b1a..4c7470d37 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -1,6 +1,8 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index b58ef7362..78cef1046 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -3,9 +3,11 @@ use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; use log::debug; -use crate::ws::mask::apply_mask; -use crate::ws::proto::{CloseCode, CloseReason, OpCode}; -use crate::ws::ProtocolError; +use super::{ + mask::apply_mask, + proto::{CloseCode, CloseReason, OpCode}, + ProtocolError, +}; /// A struct representing a WebSocket frame. #[derive(Debug)] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 222765991..b3afdec10 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = "3.0.0-beta.19" +actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" From fd412a8223168f86d0cd3aaf1ca81a9f67c8a15d Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 1 Feb 2022 00:26:34 +0300 Subject: [PATCH 322/861] `Quoter::requote` returns `Vec` (#2613) --- actix-router/CHANGES.md | 3 +++ actix-router/src/de.rs | 6 +++--- actix-router/src/quoter.rs | 43 ++++++++++++++++++++++++++++++-------- actix-router/src/url.rs | 8 +++---- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 6253b522a..1f22e5764 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- `Quoter::requote` now returns `Option>`. [#2613] + +[#2613]: https://github.com/actix/actix-web/pull/2613 ## 0.5.0-rc.2 - 2022-01-21 diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index 27aa49ef2..efafd08db 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -52,7 +52,7 @@ macro_rules! parse_value { V: Visitor<'de>, { let decoded = FULL_QUOTER - .with(|q| q.requote(self.value.as_bytes())) + .with(|q| q.requote_str_lossy(self.value)) .map(Cow::Owned) .unwrap_or(Cow::Borrowed(self.value)); @@ -332,7 +332,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) { Some(s) => visitor.visit_string(s), None => visitor.visit_borrowed_str(self.value), } @@ -342,7 +342,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { + match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) { Some(s) => visitor.visit_byte_buf(s.into()), None => visitor.visit_borrowed_bytes(self.value.as_bytes()), } diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 26ecc92cd..73b1e72dd 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -66,8 +66,13 @@ impl Quoter { /// Re-quotes... ? /// - /// Returns `None` when no modification to the original string was required. - pub fn requote(&self, val: &[u8]) -> Option { + /// Returns `None` when no modification to the original byte string was required. + /// + /// Non-ASCII bytes are accepted as valid input. + /// + /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include removing + /// the invalid sequence from the output or passing it as it is. + pub fn requote(&self, val: &[u8]) -> Option> { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; let mut idx = 0; @@ -121,7 +126,12 @@ impl Quoter { idx += 1; } - cloned.map(|data| String::from_utf8_lossy(&data).into_owned()) + cloned + } + + pub(crate) fn requote_str_lossy(&self, val: &str) -> Option { + self.requote(val.as_bytes()) + .map(|data| String::from_utf8_lossy(&data).into_owned()) } } @@ -201,14 +211,29 @@ mod tests { #[test] fn custom_quoter() { let q = Quoter::new(b"", b"+"); - assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc"); + assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c"); + assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc"); let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb"); - assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb"); + assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c"); + assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb"); + assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb"); + assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb"); + assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); + assert_eq!(q.requote(b"/a\xfe\xffb"), None); + } + + #[test] + fn non_ascii() { + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); + assert_eq!(q.requote(b"/a\xfe\xffb"), None); + } + + #[test] + fn invalid_sequences() { + let q = Quoter::new(b"%+", b"/"); + assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X"); } #[test] diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index f8d94ae4a..e7dda3fca 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -15,14 +15,14 @@ pub struct Url { impl Url { #[inline] pub fn new(uri: http::Uri) -> Url { - let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + let path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path())); Url { uri, path } } #[inline] pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url { Url { - path: quoter.requote(uri.path().as_bytes()), + path: quoter.requote_str_lossy(uri.path()), uri, } } @@ -45,13 +45,13 @@ impl Url { #[inline] pub fn update(&mut self, uri: &http::Uri) { self.uri = uri.clone(); - self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); + self.path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path())); } #[inline] pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) { self.uri = uri.clone(); - self.path = quoter.requote(uri.path().as_bytes()); + self.path = quoter.requote_str_lossy(uri.path()); } } From 9fde5b30dbf7b861cc328ba1353bc6d6cbfc2f30 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 1 Feb 2022 01:12:48 +0300 Subject: [PATCH 323/861] tweak and document router (#2612) Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 6 ++ actix-router/src/lib.rs | 2 +- actix-router/src/quoter.rs | 6 +- actix-router/src/resource.rs | 30 +++---- actix-router/src/router.rs | 152 +++++++++++++++++++---------------- src/app_service.rs | 23 ++---- src/scope.rs | 17 +--- 7 files changed, 115 insertions(+), 121 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 1f22e5764..368138603 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +- Remove unused `ResourceInfo`. [#2612] +- Add `RouterBuilder::push`. [#2612] +- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] +- Replace `Option` with `U` in `Router` API. [#2612] +- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612] - `Quoter::requote` now returns `Option>`. [#2613] +[#2612]: https://github.com/actix/actix-web/pull/2612 [#2613]: https://github.com/actix/actix-web/pull/2613 diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 22f294b9d..0febcf1ac 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -22,7 +22,7 @@ pub use self::pattern::{IntoPatterns, Patterns}; pub use self::quoter::Quoter; pub use self::resource::ResourceDef; pub use self::resource_path::{Resource, ResourcePath}; -pub use self::router::{ResourceInfo, Router, RouterBuilder}; +pub use self::router::{ResourceId, Router, RouterBuilder}; #[cfg(feature = "http")] pub use self::url::Url; diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 73b1e72dd..8a1e99e1d 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -64,14 +64,14 @@ impl Quoter { quoter } - /// Re-quotes... ? + /// Decodes safe percent-encoded sequences from `val`. /// /// Returns `None` when no modification to the original byte string was required. /// /// Non-ASCII bytes are accepted as valid input. /// - /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include removing - /// the invalid sequence from the output or passing it as it is. + /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include + /// removing the invalid sequence from the output or passing it as-is. pub fn requote(&self, val: &[u8]) -> Option> { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index c0b5522af..f3eaa9f42 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -8,10 +8,7 @@ use std::{ use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; -use crate::{ - path::{Path, PathItem}, - IntoPatterns, Patterns, Resource, ResourcePath, -}; +use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath}; const MAX_DYNAMIC_SEGMENTS: usize = 16; @@ -615,7 +612,7 @@ impl ResourceDef { } } - /// Collects dynamic segment values into `path`. + /// Collects dynamic segment values into `resource`. /// /// Returns `true` if `path` matches this resource. /// @@ -635,9 +632,9 @@ impl ResourceDef { /// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml"); /// assert_eq!(path.unprocessed(), ""); /// ``` - pub fn capture_match_info(&self, path: &mut Path) -> bool { + pub fn capture_match_info(&self, resource: &mut R) -> bool { profile_method!(capture_match_info); - self.capture_match_info_fn(path, |_, _| true, ()) + self.capture_match_info_fn(resource, |_| true) } /// Collects dynamic segment values into `resource` after matching paths and executing @@ -655,13 +652,12 @@ impl ResourceDef { /// use actix_router::{Path, ResourceDef}; /// /// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool { - /// let admin_allowed = std::env::var("ADMIN_ALLOWED").ok(); + /// let admin_allowed = std::env::var("ADMIN_ALLOWED").is_ok(); /// /// resource.capture_match_info_fn( /// path, /// // when env var is not set, reject when path contains "admin" - /// |res, admin_allowed| !res.path().contains("admin"), - /// &admin_allowed + /// |res| !(!admin_allowed && res.path().contains("admin")), /// ) /// } /// @@ -678,15 +674,10 @@ impl ResourceDef { /// assert!(!try_match(&resource, &mut path)); /// assert_eq!(path.unprocessed(), "/user/admin/stars"); /// ``` - pub fn capture_match_info_fn( - &self, - resource: &mut R, - check_fn: F, - user_data: U, - ) -> bool + pub fn capture_match_info_fn(&self, resource: &mut R, check_fn: F) -> bool where R: Resource, - F: FnOnce(&R, U) -> bool, + F: FnOnce(&R) -> bool, { profile_method!(capture_match_info_fn); @@ -762,7 +753,7 @@ impl ResourceDef { } }; - if !check_fn(resource, user_data) { + if !check_fn(resource) { return false; } @@ -857,7 +848,7 @@ impl ResourceDef { S: BuildHasher, { profile_method!(resource_path_from_map); - self.build_resource_path(path, |name| values.get(name).map(AsRef::::as_ref)) + self.build_resource_path(path, |name| values.get(name)) } /// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`. @@ -1157,6 +1148,7 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { #[cfg(test)] mod tests { use super::*; + use crate::Path; #[test] fn equivalence() { diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index 4652ef678..f0e598683 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -5,87 +5,83 @@ use crate::{IntoPatterns, Resource, ResourceDef}; #[derive(Debug, Copy, Clone, PartialEq)] pub struct ResourceId(pub u16); -/// Information about current resource -#[derive(Debug, Clone)] -pub struct ResourceInfo { - #[allow(dead_code)] - resource: ResourceId, -} - /// Resource router. -// T is the resource itself -// U is any other data needed for routing like method guards +/// +/// It matches a [routing resource](Resource) to an ordered list of _routes_. Each is defined by a +/// single [`ResourceDef`] and contains two types of custom data: +/// 1. The route _value_, of the generic type `T`. +/// 1. Some _context_ data, of the generic type `U`, which is only provided to the check function in +/// [`recognize_fn`](Self::recognize_fn). This parameter defaults to `()` and can be omitted if +/// not required. pub struct Router { - routes: Vec<(ResourceDef, T, Option)>, + routes: Vec<(ResourceDef, T, U)>, } impl Router { + /// Constructs new `RouterBuilder` with empty route list. pub fn build() -> RouterBuilder { - RouterBuilder { - resources: Vec::new(), - } + RouterBuilder { routes: Vec::new() } } + /// Finds the value in the router that matches a given [routing resource](Resource). + /// + /// The match result, including the captured dynamic segments, in the `resource`. pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> where R: Resource, { profile_method!(recognize); - - for item in self.routes.iter() { - if item.0.capture_match_info(resource.resource_path()) { - return Some((&item.1, ResourceId(item.0.id()))); - } - } - - None + self.recognize_fn(resource, |_, _| true) } + /// Same as [`recognize`](Self::recognize) but returns a mutable reference to the matched value. pub fn recognize_mut(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> where R: Resource, { profile_method!(recognize_mut); - - for item in self.routes.iter_mut() { - if item.0.capture_match_info(resource.resource_path()) { - return Some((&mut item.1, ResourceId(item.0.id()))); - } - } - - None + self.recognize_mut_fn(resource, |_, _| true) } - pub fn recognize_fn(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> + /// Finds the value in the router that matches a given [routing resource](Resource) and passes + /// an additional predicate check using context data. + /// + /// Similar to [`recognize`](Self::recognize). However, before accepting the route as matched, + /// the `check` closure is executed, passing the resource and each route's context data. If the + /// closure returns true then the match result is stored into `resource` and a reference to + /// the matched _value_ is returned. + pub fn recognize_fn(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)> where - F: Fn(&R, &Option) -> bool, R: Resource, + F: FnMut(&R, &U) -> bool, { profile_method!(recognize_checked); - for item in self.routes.iter() { - if item.0.capture_match_info_fn(resource, &check, &item.2) { - return Some((&item.1, ResourceId(item.0.id()))); + for (rdef, val, ctx) in self.routes.iter() { + if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { + return Some((val, ResourceId(rdef.id()))); } } None } + /// Same as [`recognize_fn`](Self::recognize_fn) but returns a mutable reference to the matched + /// value. pub fn recognize_mut_fn( &mut self, resource: &mut R, - check: F, + mut check: F, ) -> Option<(&mut T, ResourceId)> where - F: Fn(&R, &Option) -> bool, R: Resource, + F: FnMut(&R, &U) -> bool, { profile_method!(recognize_mut_checked); - for item in self.routes.iter_mut() { - if item.0.capture_match_info_fn(resource, &check, &item.2) { - return Some((&mut item.1, ResourceId(item.0.id()))); + for (rdef, val, ctx) in self.routes.iter_mut() { + if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { + return Some((val, ResourceId(rdef.id()))); } } @@ -93,49 +89,69 @@ impl Router { } } +/// Builder for an ordered [routing](Router) list. pub struct RouterBuilder { - resources: Vec<(ResourceDef, T, Option)>, + routes: Vec<(ResourceDef, T, U)>, } impl RouterBuilder { - /// Register resource for specified path. - pub fn path( + /// Adds a new route to the end of the routing list. + /// + /// Returns mutable references to elements of the new route. + pub fn push( &mut self, - path: P, - resource: T, - ) -> &mut (ResourceDef, T, Option) { - profile_method!(path); - - self.resources - .push((ResourceDef::new(path), resource, None)); - self.resources.last_mut().unwrap() - } - - /// Register resource for specified path prefix. - pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option) { - profile_method!(prefix); - - self.resources - .push((ResourceDef::prefix(prefix), resource, None)); - self.resources.last_mut().unwrap() - } - - /// Register resource for ResourceDef - pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option) { - profile_method!(rdef); - - self.resources.push((rdef, resource, None)); - self.resources.last_mut().unwrap() + rdef: ResourceDef, + val: T, + ctx: U, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(push); + self.routes.push((rdef, val, ctx)); + self.routes + .last_mut() + .map(|(rdef, val, ctx)| (rdef, val, ctx)) + .unwrap() } /// Finish configuration and create router instance. pub fn finish(self) -> Router { Router { - routes: self.resources, + routes: self.routes, } } } +/// Convenience methods provided when context data impls [`Default`] +impl RouterBuilder +where + U: Default, +{ + /// Registers resource for specified path. + pub fn path( + &mut self, + path: impl IntoPatterns, + val: T, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(path); + self.push(ResourceDef::new(path), val, U::default()) + } + + /// Registers resource for specified path prefix. + pub fn prefix( + &mut self, + prefix: impl IntoPatterns, + val: T, + ) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(prefix); + self.push(ResourceDef::prefix(prefix), val, U::default()) + } + + /// Registers resource for [`ResourceDef`]. + pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) { + profile_method!(rdef); + self.push(rdef, val, U::default()) + } +} + #[cfg(test)] mod tests { use crate::path::Path; diff --git a/src/app_service.rs b/src/app_service.rs index dbd718330..3ef31ac75 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -21,8 +21,6 @@ use crate::{ Error, HttpResponse, }; -type Guards = Vec>; - /// Service factory to convert `Request` to a `ServiceRequest`. /// /// It also executes data factories. @@ -244,7 +242,7 @@ pub struct AppRoutingFactory { [( ResourceDef, BoxedHttpServiceFactory, - RefCell>, + RefCell>>>, )], >, default: Rc, @@ -262,7 +260,7 @@ impl ServiceFactory for AppRoutingFactory { // construct all services factory future with it's resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); - let guards = guards.borrow_mut().take(); + let guards = guards.borrow_mut().take().unwrap_or_default(); let factory_fut = factory.new_service(()); async move { let service = factory_fut.await?; @@ -283,7 +281,7 @@ impl ServiceFactory for AppRoutingFactory { .collect::, _>>()? .drain(..) .fold(Router::build(), |mut router, (path, guards, service)| { - router.rdef(path, service).2 = guards; + router.push(path, service, guards); router }) .finish(); @@ -295,7 +293,7 @@ impl ServiceFactory for AppRoutingFactory { /// The Actix Web router default entry point. pub struct AppRouting { - router: Router, + router: Router>>, default: BoxedHttpService, } @@ -308,17 +306,8 @@ impl Service for AppRouting { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { - if let Some(ref guards) = guards { - let guard_ctx = req.guard_ctx(); - - for guard in guards { - if !guard.check(&guard_ctx) { - return false; - } - } - } - - true + let guard_ctx = req.guard_ctx(); + guards.iter().all(|guard| guard.check(&guard_ctx)) }); if let Some((srv, _info)) = res { diff --git a/src/scope.rs b/src/scope.rs index dad727430..0fcc83d70 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -467,7 +467,7 @@ impl ServiceFactory for ScopeFactory { // construct all services factory future with it's resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); - let guards = guards.borrow_mut().take(); + let guards = guards.borrow_mut().take().unwrap_or_default(); let factory_fut = factory.new_service(()); async move { let service = factory_fut.await?; @@ -485,7 +485,7 @@ impl ServiceFactory for ScopeFactory { .collect::, _>>()? .drain(..) .fold(Router::build(), |mut router, (path, guards, service)| { - router.rdef(path, service).2 = guards; + router.push(path, service, guards); router }) .finish(); @@ -509,17 +509,8 @@ impl Service for ScopeService { fn call(&self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_fn(&mut req, |req, guards| { - if let Some(ref guards) = guards { - let guard_ctx = req.guard_ctx(); - - for guard in guards { - if !guard.check(&guard_ctx) { - return false; - } - } - } - - true + let guard_ctx = req.guard_ctx(); + guards.iter().all(|guard| guard.check(&guard_ctx)) }); if let Some((srv, _info)) = res { From 9777653dc0abe039b72b3a1582b009232c136d77 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:20:53 +0000 Subject: [PATCH 324/861] prep readme for rc release --- README.md | 30 +++++++++++++----------------- scripts/ci-test | 30 ++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c3ea70f2c..cc761a98e 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,13 @@ ## Features -- Supports *HTTP/1.x* and *HTTP/2* +- Supports _HTTP/1.x_ and _HTTP/2_ - Streaming and pipelining +- Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros +- Full [Tokio](https://tokio.rs) compatibility - Keep-alive and slow requests handling - Client/server [WebSockets](https://actix.rs/docs/websockets/) support - Transparent content compression/decompression (br, gzip, deflate, zstd) -- Powerful [request routing](https://actix.rs/docs/url-dispatch/) - Multipart streams - Static assets - SSL support using OpenSSL or Rustls @@ -47,7 +48,7 @@ Dependencies: ```toml [dependencies] -actix-web = "3" +actix-web = "4.0.0-rc.1" ``` Code: @@ -56,14 +57,15 @@ Code: use actix_web::{get, web, App, HttpServer, Responder}; #[get("/{id}/{name}/index.html")] -async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder { +async fn index(params: web::Path<(u32, String)>) -> impl Responder { + let (id, name) = params.into_inner(); format!("Hello {}! id:{}", name, id) } -#[actix_web::main] +#[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(index)) - .bind("127.0.0.1:8080")? + .bind(("127.0.0.1", 8080))? .run() .await } @@ -84,24 +86,18 @@ async fn main() -> std::io::Result<()> { - [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) - [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. +You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. ## Benchmarks -One of the fastest web frameworks available according to the -[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). +One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). ## License -This project is licensed under either of +This project is licensed under either of the following licenses, at your option: -- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - [http://www.apache.org/licenses/LICENSE-2.0]) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or - [http://opensource.org/licenses/MIT]) - -at your option. +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) ## Code of Conduct diff --git a/scripts/ci-test b/scripts/ci-test index 567012d33..8b7e3d12d 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -13,16 +13,26 @@ save_exit_code() { } save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls -save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture -save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls +# save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture +# save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture -save_exit_code cargo test --workspace --doc +# save_exit_code cargo test --workspace --doc + +if [ "$EXIT" = "0" ]; then + PASSED="All tests passed!" + + if [ "$(command -v figlet)" ]; then + figlet "$PASSED" + else + echo "$PASSED" + fi +fi exit $EXIT From 47f5faf26e08671e0c7fce53945a33ed9a258493 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:21:30 +0000 Subject: [PATCH 325/861] prepare actix-router release 0.5.0-rc.3 --- Cargo.toml | 2 +- actix-router/CHANGES.md | 3 +++ actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38c8512bc..0560ec190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } -actix-router = "0.5.0-rc.2" +actix-router = "0.5.0-rc.3" actix-web-codegen = "0.5.0-rc.1" ahash = "0.7" diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 368138603..ff9d8b4ab 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.5.0-rc.3 - 2022-01-31 - Remove unused `ResourceInfo`. [#2612] - Add `RouterBuilder::push`. [#2612] - Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 0d4e4f897..647a34479 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 3ee2fbd02..eb2e5536d 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" proc-macro = true [dependencies] -actix-router = "0.5.0-beta.4" +actix-router = "0.5.0-rc.3" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "parsing"] } From 21f57caf4ac1e2287e211d0e2f1f245820e49b70 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:22:40 +0000 Subject: [PATCH 326/861] prepare actix-http release 3.0.0-rc.1 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0560ec190..3799febe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ actix-service = "2.0.0" actix-utils = "3.0.0" actix-tls = { version = "3.0.0", default-features = false, optional = true } -actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" actix-web-codegen = "0.5.0-rc.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 68a3399a5..5faa0ebad 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-beta.21", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 9bd3e9f9b..15fd298c1 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 38bec78ba..7f7af23a8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-rc.1 - 2022-01-31 ### Added - Implement `Default` for `KeepAlive`. [#2611] - Implement `From` for `KeepAlive`. [#2611] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f68eda074..959eefd8d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.19" +version = "3.0.0-rc.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 9f275f597..0087fabe3 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.19)](https://docs.rs/actix-http/3.0.0-beta.19) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.1)](https://docs.rs/actix-http/3.0.0-rc.1) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.19/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.19) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 327252dbf..08c15da9c 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index e2c75b01e..6d70db0b9 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3c28c0a15..51673a177 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-beta.19" +actix-http = "3.0.0-rc.1" actix-web = { version = "4.0.0-beta.21", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b3afdec10..6d6e1c134 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-beta.19", features = ["openssl"] } +actix-http = { version = "3.0.0-rc.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } From c3c7eb8df9b52b33fc8826fda08d7758075ffd19 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:23:33 +0000 Subject: [PATCH 327/861] prepare actix-web release 4.0.0-rc.1 --- CHANGES.md | 3 +++ Cargo.toml | 2 +- README.md | 4 ++-- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c00bc7198..a7e0a0309 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-rc.1 - 2022-01-31 ### Changed - Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] - Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] diff --git a/Cargo.toml b/Cargo.toml index 3799febe7..0f5b87810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.21" +version = "4.0.0-rc.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/README.md b/README.md index cc761a98e..f99a7be23 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.21)](https://docs.rs/actix-web/4.0.0-beta.21) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.1)](https://docs.rs/actix-web/4.0.0-rc.1) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.21/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.21) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.1)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 5faa0ebad..77e9ea0f6 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-rc.1" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-beta.21", default-features = false } +actix-web = { version = "4.0.0-rc.1", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.11" -actix-web = "4.0.0-beta.21" +actix-web = "4.0.0-rc.1" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 15fd298c1..4aa7a58c9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] } actix-http = "3.0.0-rc.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 959eefd8d..f4f178ee2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.9", optional = true } actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-beta.21" +actix-web = "4.0.0-rc.1" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 08c15da9c..23c5ba0df 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.21", default-features = false } +actix-web = { version = "4.0.0-rc.1", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 6d70db0b9..def5a5825 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.11" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.21", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.19", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 51673a177..1c08ff733 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-rc.1" -actix-web = { version = "4.0.0-beta.21", default-features = false } +actix-web = { version = "4.0.0-rc.1", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index eb2e5536d..1147ecc0d 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.11" actix-utils = "3.0.0" -actix-web = "4.0.0-beta.21" +actix-web = "4.0.0-rc.1" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 6d6e1c134..ee75e1adc 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-beta.21", features = ["openssl"] } +actix-web = { version = "4.0.0-rc.1", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" From 102720d398d46afc32ee9a6af5d301d5bde61c26 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:32:09 +0000 Subject: [PATCH 328/861] prepare awc release 3.0.0-beta.20 --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f5b87810..a45ddea25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.15" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.19", features = ["openssl"] } +awc = { version = "3.0.0-beta.20", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 4aa7a58c9..d0ebde3f1 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3.0.0" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2" -awc = { version = "3.0.0-beta.19", default-features = false } +awc = { version = "3.0.0-beta.20", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index def5a5825..1780bbf98 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.19", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 1c08ff733..7f39cf502 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.11" -awc = { version = "3.0.0-beta.19", default-features = false } +awc = { version = "3.0.0-beta.20", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e12dc8c27..05e524fad 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.20 - 2022-01-31 +- No significant changes since `3.0.0-beta.19`. + + ## 3.0.0-beta.19 - 2022-01-21 - No significant changes since `3.0.0-beta.18`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ee75e1adc..ffbfca186 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.19" +version = "3.0.0-beta.20" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index 97e555d0d..2546ceeec 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.19)](https://docs.rs/awc/3.0.0-beta.19) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.20)](https://docs.rs/awc/3.0.0-beta.20) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.19/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.19) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.20/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.20) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 66243717b3789b4fe274c8262b238e41ecade434 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:32:52 +0000 Subject: [PATCH 329/861] prepare actix-files release 0.6.0-beta.16 --- Cargo.toml | 2 +- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a45ddea25..86b3082cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0-beta.15" +actix-files = "0.6.0-beta.16" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.20", features = ["openssl"] } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index fa9647e62..f0234b561 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.16 - 2022-01-31 +- No significant changes since `0.6.0-beta.15`. + + ## 0.6.0-beta.15 - 2022-01-21 - No significant changes since `0.6.0-beta.14`. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 77e9ea0f6..cdf538471 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.15" +version = "0.6.0-beta.16" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index 8395957c5..669efc0ab 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.15)](https://docs.rs/actix-files/0.6.0-beta.15) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.16)](https://docs.rs/actix-files/0.6.0-beta.16) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.15/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.15) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.16/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.16) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 7f4b44c25876bb42c582327b111697a051ded3dc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:33:11 +0000 Subject: [PATCH 330/861] prepare actix-multipart release 0.4.0-beta.13 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 92feade3b..71c8e958f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.13 - 2022-01-31 +- No significant changes since `0.4.0-beta.12`. + + ## 0.4.0-beta.12 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 23c5ba0df..2c8a28acb 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.12" +version = "0.4.0-beta.13" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 91cd8a6e9..b517e8ded 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.12)](https://docs.rs/actix-multipart/0.4.0-beta.12) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.13)](https://docs.rs/actix-multipart/0.4.0-beta.13) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.12/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.12) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.13/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.13) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From bf282472abab4e3ba9e58dcaf48bd356f9ec6cb7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:33:38 +0000 Subject: [PATCH 331/861] prepare actix-http-test release 3.0.0-beta.12 --- actix-http-test/CHANGES.md | 4 ++++ actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index b62281798..a909b1d6a 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.12 - 2022-01-31 +- No significant changes since `3.0.0-beta.11`. + + ## 3.0.0-beta.11 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index d0ebde3f1..1e2f90249 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.11" +version = "3.0.0-beta.12" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 10c04b368..bf9dddfa2 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.11)](https://docs.rs/actix-http-test/3.0.0-beta.11) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http-test/3.0.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.11) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.12) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f4f178ee2..b592c98da 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.9", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } actix-web = "4.0.0-rc.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 1780bbf98..2533fbf34 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" actix-http = "3.0.0-rc.1" -actix-http-test = "3.0.0-beta.11" +actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ffbfca186..1c05c3b5e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-http = { version = "3.0.0-rc.1", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } From 20609e93fdff7f8ef96bb214cd16b1c2ed1627ed Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:34:59 +0000 Subject: [PATCH 332/861] prepare actix-test release 0.1.0-beta.12 --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86b3082cd..3b238e397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.16" -actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.20", features = ["openssl"] } brotli = "3.3.3" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index cdf538471..425475e41 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,6 +43,6 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.11" +actix-test = "0.1.0-beta.12" actix-web = "4.0.0-rc.1" tempfile = "3.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 3877f4fbf..0c8fc996b 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.1.0-beta.12 - 2022-01-31 - Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611] [#2611]: https://github.com/actix/actix-web/pull/2611 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 2533fbf34..632092bbf 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.11" +version = "0.1.0-beta.12" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7f39cf502..9c4f66fe8 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.11" +actix-test = "0.1.0-beta.12" awc = { version = "3.0.0-beta.20", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 1147ecc0d..0edc249e2 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ syn = { version = "1", features = ["full", "parsing"] } [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.11" +actix-test = "0.1.0-beta.12" actix-utils = "3.0.0" actix-web = "4.0.0-rc.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1c05c3b5e..f2bf7526d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-http = { version = "3.0.0-rc.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" -actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-rc.1", features = ["openssl"] } From a66cd38ec547c3929bc0af863f141def2172fb73 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 31 Jan 2022 22:35:18 +0000 Subject: [PATCH 333/861] prepare actix-web-actors release 4.0.0-beta.11 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 74ab3c785..a8ff2701d 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.11 - 2022-01-31 +- No significant changes since `4.0.0-beta.10`. + + ## 4.0.0-beta.10 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 9c4f66fe8..10690102f 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.10" +version = "4.0.0-beta.11" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 60e6a9bd9..4a491c29a 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.10)](https://docs.rs/actix-web-actors/4.0.0-beta.10) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.11)](https://docs.rs/actix-web-actors/4.0.0-beta.11) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 5469b02638530b4b4ff532113b22b776a4c25f8c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 00:12:42 +0000 Subject: [PATCH 334/861] prepare actix-web-codegen release 0.5.0-rc.2 --- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b238e397..9610539de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" -actix-web-codegen = "0.5.0-rc.1" +actix-web-codegen = "0.5.0-rc.2" ahash = "0.7" bytes = "1" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index c044ff74d..9483f1b35 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.5.0-rc.2 - 2022-02-01 +- No significant changes since `0.5.0-rc.1`. + + ## 0.5.0-rc.1 - 2022-01-04 - Minimum supported Rust version (MSRV) is now 1.54. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 0edc249e2..392d1cf8c 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 1fd97184c..f4e8344f3 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-rc.1)](https://docs.rs/actix-web-codegen/0.5.0-rc.1) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-rc.2)](https://docs.rs/actix-web-codegen/0.5.0-rc.2) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1) +[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.2/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.2) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 30aa64ea321f498622f7175b87f727cae0935448 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 00:23:58 +0000 Subject: [PATCH 335/861] update dep graphs --- .../{dependency-graphs.md => README.md} | 0 docs/graphs/web-focus.dot | 1 + docs/graphs/web-only.dot | 34 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) rename docs/graphs/{dependency-graphs.md => README.md} (100%) diff --git a/docs/graphs/dependency-graphs.md b/docs/graphs/README.md similarity index 100% rename from docs/graphs/dependency-graphs.md rename to docs/graphs/README.md diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 63b3eaa82..16b2d415e 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -19,6 +19,7 @@ digraph { "web" -> { "codec" "service" "utils" "router" "rt" "server" "macros" "web-codegen" "http" "awc" } "web" -> { "tls" }[color=blue] // optional + "web-codegen" -> { "router" } "awc" -> { "codec" "service" "http" "rt" } "web-actors" -> { "actix" "web" "http" "codec" } "multipart" -> { "web" "service" "utils" } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index b27dd0943..dad285bdf 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -2,23 +2,23 @@ digraph { subgraph cluster_web { label="actix/actix-web" "awc" - "actix-web" - "actix-files" - "actix-http" - "actix-multipart" - "actix-web-actors" - "actix-web-codegen" - "actix-http-test" - "actix-test" - "actix-router" + "web" + "files" + "http" + "multipart" + "web-actors" + "web-codegen" + "http-test" + "test" + "router" } - "actix-web" -> { "actix-web-codegen" "actix-http" "actix-router" } - "awc" -> { "actix-http" } - "actix-web-codegen" -> { "actix-router" } - "actix-web-actors" -> { "actix" "actix-web" "actix-http" } - "actix-multipart" -> { "actix-web" } - "actix-files" -> { "actix-web" } - "actix-http-test" -> { "awc" } - "actix-test" -> { "actix-web" "awc" "actix-http-test" } + "web" -> { "web-codegen" "http" "router" } + "awc" -> { "http" } + "web-codegen" -> { "router" }[color = red] + "web-actors" -> { "actix" "web" "http" } + "multipart" -> { "web" } + "files" -> { "web" } + "http-test" -> { "awc" } + "test" -> { "web" "awc" "http-test" } } From bcdde1d4ea2f4bf6c01ccf8b5b6118f7c9a3b2bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 00:30:41 +0000 Subject: [PATCH 336/861] move actix-web to own dir --- CHANGES.md | 1031 +--------------- Cargo.toml | 147 +-- README.md | 106 +- actix-web/CHANGES.md | 1032 +++++++++++++++++ actix-web/Cargo.toml | 144 +++ actix-web/LICENSE-APACHE | 1 + actix-web/LICENSE-MIT | 1 + MIGRATION.md => actix-web/MIGRATION.md | 0 actix-web/README.md | 105 ++ {benches => actix-web/benches}/responder.rs | 0 {benches => actix-web/benches}/server.rs | 0 {benches => actix-web/benches}/service.rs | 0 {examples => actix-web/examples}/README.md | 0 {examples => actix-web/examples}/basic.rs | 0 .../examples}/on-connect.rs | 0 {examples => actix-web/examples}/uds.rs | 0 {src => actix-web/src}/app.rs | 0 {src => actix-web/src}/app_service.rs | 0 {src => actix-web/src}/config.rs | 0 {src => actix-web/src}/data.rs | 0 {src => actix-web/src}/dev.rs | 0 {src => actix-web/src}/error/error.rs | 0 {src => actix-web/src}/error/internal.rs | 0 {src => actix-web/src}/error/macros.rs | 0 {src => actix-web/src}/error/mod.rs | 0 .../src}/error/response_error.rs | 0 {src => actix-web/src}/extract.rs | 0 {src => actix-web/src}/guard.rs | 0 {src => actix-web/src}/handler.rs | 0 {src => actix-web/src}/helpers.rs | 0 {src => actix-web/src}/http/header/accept.rs | 0 .../src}/http/header/accept_charset.rs | 0 .../src}/http/header/accept_encoding.rs | 0 .../src}/http/header/accept_language.rs | 0 {src => actix-web/src}/http/header/allow.rs | 0 .../src}/http/header/any_or_some.rs | 0 .../src}/http/header/cache_control.rs | 0 .../src}/http/header/content_disposition.rs | 0 .../src}/http/header/content_language.rs | 0 .../src}/http/header/content_range.rs | 0 .../src}/http/header/content_type.rs | 0 {src => actix-web/src}/http/header/date.rs | 0 .../src}/http/header/encoding.rs | 0 {src => actix-web/src}/http/header/entity.rs | 0 {src => actix-web/src}/http/header/etag.rs | 0 {src => actix-web/src}/http/header/expires.rs | 0 .../src}/http/header/if_match.rs | 0 .../src}/http/header/if_modified_since.rs | 0 .../src}/http/header/if_none_match.rs | 0 .../src}/http/header/if_range.rs | 0 .../src}/http/header/if_unmodified_since.rs | 0 .../src}/http/header/last_modified.rs | 0 {src => actix-web/src}/http/header/macros.rs | 0 {src => actix-web/src}/http/header/mod.rs | 0 .../src}/http/header/preference.rs | 0 {src => actix-web/src}/http/header/range.rs | 0 {src => actix-web/src}/http/mod.rs | 0 {src => actix-web/src}/info.rs | 0 {src => actix-web/src}/lib.rs | 0 {src => actix-web/src}/middleware/compat.rs | 0 {src => actix-web/src}/middleware/compress.rs | 0 .../src}/middleware/condition.rs | 0 .../src}/middleware/default_headers.rs | 0 .../src}/middleware/err_handlers.rs | 0 {src => actix-web/src}/middleware/logger.rs | 0 {src => actix-web/src}/middleware/mod.rs | 0 {src => actix-web/src}/middleware/noop.rs | 0 .../src}/middleware/normalize.rs | 0 {src => actix-web/src}/request.rs | 0 {src => actix-web/src}/request_data.rs | 0 {src => actix-web/src}/resource.rs | 0 {src => actix-web/src}/response/builder.rs | 0 .../src}/response/customize_responder.rs | 0 {src => actix-web/src}/response/http_codes.rs | 0 {src => actix-web/src}/response/mod.rs | 0 {src => actix-web/src}/response/responder.rs | 0 {src => actix-web/src}/response/response.rs | 0 {src => actix-web/src}/rmap.rs | 0 {src => actix-web/src}/route.rs | 0 {src => actix-web/src}/scope.rs | 0 {src => actix-web/src}/server.rs | 0 {src => actix-web/src}/service.rs | 0 {src => actix-web/src}/test/mod.rs | 0 {src => actix-web/src}/test/test_request.rs | 0 {src => actix-web/src}/test/test_services.rs | 0 {src => actix-web/src}/test/test_utils.rs | 0 {src => actix-web/src}/types/either.rs | 0 {src => actix-web/src}/types/form.rs | 0 {src => actix-web/src}/types/header.rs | 0 {src => actix-web/src}/types/json.rs | 0 {src => actix-web/src}/types/mod.rs | 0 {src => actix-web/src}/types/path.rs | 0 {src => actix-web/src}/types/payload.rs | 0 {src => actix-web/src}/types/query.rs | 0 {src => actix-web/src}/types/readlines.rs | 0 {src => actix-web/src}/web.rs | 0 {tests => actix-web/tests}/compression.rs | 0 {tests => actix-web/tests}/fixtures/lorem.txt | 0 .../tests}/fixtures/lorem.txt.br | Bin .../tests}/fixtures/lorem.txt.gz | Bin .../tests}/fixtures/lorem.txt.xz | Bin .../tests}/fixtures/lorem.txt.zst | Bin .../tests}/test-macro-import-conflict.rs | 0 .../tests}/test_error_propagation.rs | 0 {tests => actix-web/tests}/test_httpserver.rs | 0 {tests => actix-web/tests}/test_server.rs | 0 {tests => actix-web/tests}/test_weird_poll.rs | 0 {tests => actix-web/tests}/utils.rs | 0 {tests => actix-web/tests}/weird_poll.rs | 0 109 files changed, 1287 insertions(+), 1280 deletions(-) mode change 100644 => 120000 README.md create mode 100644 actix-web/CHANGES.md create mode 100644 actix-web/Cargo.toml create mode 120000 actix-web/LICENSE-APACHE create mode 120000 actix-web/LICENSE-MIT rename MIGRATION.md => actix-web/MIGRATION.md (100%) create mode 100644 actix-web/README.md rename {benches => actix-web/benches}/responder.rs (100%) rename {benches => actix-web/benches}/server.rs (100%) rename {benches => actix-web/benches}/service.rs (100%) rename {examples => actix-web/examples}/README.md (100%) rename {examples => actix-web/examples}/basic.rs (100%) rename {examples => actix-web/examples}/on-connect.rs (100%) rename {examples => actix-web/examples}/uds.rs (100%) rename {src => actix-web/src}/app.rs (100%) rename {src => actix-web/src}/app_service.rs (100%) rename {src => actix-web/src}/config.rs (100%) rename {src => actix-web/src}/data.rs (100%) rename {src => actix-web/src}/dev.rs (100%) rename {src => actix-web/src}/error/error.rs (100%) rename {src => actix-web/src}/error/internal.rs (100%) rename {src => actix-web/src}/error/macros.rs (100%) rename {src => actix-web/src}/error/mod.rs (100%) rename {src => actix-web/src}/error/response_error.rs (100%) rename {src => actix-web/src}/extract.rs (100%) rename {src => actix-web/src}/guard.rs (100%) rename {src => actix-web/src}/handler.rs (100%) rename {src => actix-web/src}/helpers.rs (100%) rename {src => actix-web/src}/http/header/accept.rs (100%) rename {src => actix-web/src}/http/header/accept_charset.rs (100%) rename {src => actix-web/src}/http/header/accept_encoding.rs (100%) rename {src => actix-web/src}/http/header/accept_language.rs (100%) rename {src => actix-web/src}/http/header/allow.rs (100%) rename {src => actix-web/src}/http/header/any_or_some.rs (100%) rename {src => actix-web/src}/http/header/cache_control.rs (100%) rename {src => actix-web/src}/http/header/content_disposition.rs (100%) rename {src => actix-web/src}/http/header/content_language.rs (100%) rename {src => actix-web/src}/http/header/content_range.rs (100%) rename {src => actix-web/src}/http/header/content_type.rs (100%) rename {src => actix-web/src}/http/header/date.rs (100%) rename {src => actix-web/src}/http/header/encoding.rs (100%) rename {src => actix-web/src}/http/header/entity.rs (100%) rename {src => actix-web/src}/http/header/etag.rs (100%) rename {src => actix-web/src}/http/header/expires.rs (100%) rename {src => actix-web/src}/http/header/if_match.rs (100%) rename {src => actix-web/src}/http/header/if_modified_since.rs (100%) rename {src => actix-web/src}/http/header/if_none_match.rs (100%) rename {src => actix-web/src}/http/header/if_range.rs (100%) rename {src => actix-web/src}/http/header/if_unmodified_since.rs (100%) rename {src => actix-web/src}/http/header/last_modified.rs (100%) rename {src => actix-web/src}/http/header/macros.rs (100%) rename {src => actix-web/src}/http/header/mod.rs (100%) rename {src => actix-web/src}/http/header/preference.rs (100%) rename {src => actix-web/src}/http/header/range.rs (100%) rename {src => actix-web/src}/http/mod.rs (100%) rename {src => actix-web/src}/info.rs (100%) rename {src => actix-web/src}/lib.rs (100%) rename {src => actix-web/src}/middleware/compat.rs (100%) rename {src => actix-web/src}/middleware/compress.rs (100%) rename {src => actix-web/src}/middleware/condition.rs (100%) rename {src => actix-web/src}/middleware/default_headers.rs (100%) rename {src => actix-web/src}/middleware/err_handlers.rs (100%) rename {src => actix-web/src}/middleware/logger.rs (100%) rename {src => actix-web/src}/middleware/mod.rs (100%) rename {src => actix-web/src}/middleware/noop.rs (100%) rename {src => actix-web/src}/middleware/normalize.rs (100%) rename {src => actix-web/src}/request.rs (100%) rename {src => actix-web/src}/request_data.rs (100%) rename {src => actix-web/src}/resource.rs (100%) rename {src => actix-web/src}/response/builder.rs (100%) rename {src => actix-web/src}/response/customize_responder.rs (100%) rename {src => actix-web/src}/response/http_codes.rs (100%) rename {src => actix-web/src}/response/mod.rs (100%) rename {src => actix-web/src}/response/responder.rs (100%) rename {src => actix-web/src}/response/response.rs (100%) rename {src => actix-web/src}/rmap.rs (100%) rename {src => actix-web/src}/route.rs (100%) rename {src => actix-web/src}/scope.rs (100%) rename {src => actix-web/src}/server.rs (100%) rename {src => actix-web/src}/service.rs (100%) rename {src => actix-web/src}/test/mod.rs (100%) rename {src => actix-web/src}/test/test_request.rs (100%) rename {src => actix-web/src}/test/test_services.rs (100%) rename {src => actix-web/src}/test/test_utils.rs (100%) rename {src => actix-web/src}/types/either.rs (100%) rename {src => actix-web/src}/types/form.rs (100%) rename {src => actix-web/src}/types/header.rs (100%) rename {src => actix-web/src}/types/json.rs (100%) rename {src => actix-web/src}/types/mod.rs (100%) rename {src => actix-web/src}/types/path.rs (100%) rename {src => actix-web/src}/types/payload.rs (100%) rename {src => actix-web/src}/types/query.rs (100%) rename {src => actix-web/src}/types/readlines.rs (100%) rename {src => actix-web/src}/web.rs (100%) rename {tests => actix-web/tests}/compression.rs (100%) rename {tests => actix-web/tests}/fixtures/lorem.txt (100%) rename {tests => actix-web/tests}/fixtures/lorem.txt.br (100%) rename {tests => actix-web/tests}/fixtures/lorem.txt.gz (100%) rename {tests => actix-web/tests}/fixtures/lorem.txt.xz (100%) rename {tests => actix-web/tests}/fixtures/lorem.txt.zst (100%) rename {tests => actix-web/tests}/test-macro-import-conflict.rs (100%) rename {tests => actix-web/tests}/test_error_propagation.rs (100%) rename {tests => actix-web/tests}/test_httpserver.rs (100%) rename {tests => actix-web/tests}/test_server.rs (100%) rename {tests => actix-web/tests}/test_weird_poll.rs (100%) rename {tests => actix-web/tests}/utils.rs (100%) rename {tests => actix-web/tests}/weird_poll.rs (100%) diff --git a/CHANGES.md b/CHANGES.md index a7e0a0309..d95c477fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,1032 +1,5 @@ # Changes -## Unreleased - 2021-xx-xx +Changelogs are kept separately for each crate in this repo. - -## 4.0.0-rc.1 - 2022-01-31 -### Changed -- Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] -- Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] - -### Removed -- `impl Future for HttpResponse`. [#2601] - -[#2601]: https://github.com/actix/actix-web/pull/2601 -[#2611]: https://github.com/actix/actix-web/pull/2611 - - -## 4.0.0-beta.21 - 2022-01-21 -### Added -- `HttpResponse::add_removal_cookie`. [#2586] -- `Logger::log_target`. [#2594] - -### Removed -- `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] -- `HttpRequestBuilder::del_cookie`. [#2591] - -[#2585]: https://github.com/actix/actix-web/pull/2585 -[#2586]: https://github.com/actix/actix-web/pull/2586 -[#2591]: https://github.com/actix/actix-web/pull/2591 -[#2594]: https://github.com/actix/actix-web/pull/2594 - - -## 4.0.0-beta.20 - 2022-01-14 -### Added -- `GuardContext::header` [#2569] -- `ServiceConfig::configure` to allow easy nesting of configuration functions. [#1988] - -### Changed -- `HttpResponse` can now be used as a `Responder` with any body type. [#2567] -- `Result` extractor wrapper can now convert error types. [#2581] -- Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581] -- Maximum number of handler extractors has increased to 12. [#2582] -- Removed bound `::Error: Debug` in test utility functions in order to support returning opaque apps. [#2584] - -[#1988]: https://github.com/actix/actix-web/pull/1988 -[#2567]: https://github.com/actix/actix-web/pull/2567 -[#2569]: https://github.com/actix/actix-web/pull/2569 -[#2581]: https://github.com/actix/actix-web/pull/2581 -[#2582]: https://github.com/actix/actix-web/pull/2582 -[#2584]: https://github.com/actix/actix-web/pull/2584 - - -## 4.0.0-beta.19 - 2022-01-04 -### Added -- `impl Hash` for `http::header::Encoding`. [#2501] -- `AcceptEncoding::negotiate()`. [#2501] - -### Changed -- `AcceptEncoding::preference` now returns `Option>`. [#2501] -- Rename methods `BodyEncoding::{encoding => encode_with, get_encoding => preferred_encoding}`. [#2501] -- `http::header::Encoding` now only represents `Content-Encoding` types. [#2501] - -### Fixed -- Auto-negotiation of content encoding is more fault-tolerant when using the `Compress` middleware. [#2501] - -### Removed -- `Compress::new`; restricting compression algorithm is done through feature flags. [#2501] -- `BodyEncoding` trait; signalling content encoding is now only done via the `Content-Encoding` header. [#2565] - -[#2501]: https://github.com/actix/actix-web/pull/2501 -[#2565]: https://github.com/actix/actix-web/pull/2565 - - -## 4.0.0-beta.18 - 2021-12-29 -### Changed -- Update `cookie` dependency (re-exported) to `0.16`. [#2555] -- Minimum supported Rust version (MSRV) is now 1.54. - -### Security -- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. - -[#2555]: https://github.com/actix/actix-web/pull/2555 -[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html - - -## 4.0.0-beta.17 - 2021-12-29 -### Added -- `guard::GuardContext` for use with the `Guard` trait. [#2552] -- `ServiceRequest::guard_ctx` for obtaining a guard context. [#2552] - -### Changed -- `Guard` trait now receives a `&GuardContext`. [#2552] -- `guard::fn_guard` functions now receives a `&GuardContext`. [#2552] -- Some guards now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552] -- The `Not` guard is now generic over the type of guard it wraps. [#2552] - -### Fixed -- Rename `ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554] -- `ConnectionInfo::peer_addr` will not return the port number. [#2554] -- `ConnectionInfo::realip_remote_addr` will not return the port number if sourcing the IP from the peer's socket address. [#2554] - -[#2552]: https://github.com/actix/actix-web/pull/2552 -[#2554]: https://github.com/actix/actix-web/pull/2554 - - -## 4.0.0-beta.16 - 2021-12-27 -### Changed -- No longer require `Scope` service body type to be boxed. [#2523] -- No longer require `Resource` service body type to be boxed. [#2526] - -[#2523]: https://github.com/actix/actix-web/pull/2523 -[#2526]: https://github.com/actix/actix-web/pull/2526 - - -## 4.0.0-beta.15 - 2021-12-17 -### Added -- Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] -- Implement `Debug` for `DefaultHeaders`. [#2510] - -### Changed -- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] -- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] -- Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] -- Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] -- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] -- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] -- Relax body type and error bounds on test utilities. [#2518] - -### Removed -- Top-level `EitherExtractError` export. [#2510] -- Conversion implementations for `either` crate. [#2516] -- `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] - -[#2510]: https://github.com/actix/actix-web/pull/2510 -[#2515]: https://github.com/actix/actix-web/pull/2515 -[#2516]: https://github.com/actix/actix-web/pull/2516 -[#2518]: https://github.com/actix/actix-web/pull/2518 - - -## 4.0.0-beta.14 - 2021-12-11 -### Added -- Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] -- `AcceptEncoding` typed header. [#2482] -- `Range` typed header. [#2485] -- `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] -- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] -- `HttpRequest::{req_data,req_data_mut}`. [#2487] -- `ServiceResponse::into_parts`. [#2499] - -### Changed -- Rename `Accept::{mime_precedence => ranked}`. [#2480] -- Rename `Accept::{mime_preference => preference}`. [#2480] -- Un-deprecate `App::data_factory`. [#2484] -- `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] -- Remove `B` (body) type parameter on `App`. [#2493] -- Add `B` (body) type parameter on `Scope`. [#2492] -- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] - -### Fixed -- Accept wildcard `*` items in `AcceptLanguage`. [#2480] -- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] -- Typed headers containing lists that require one or more items now enforce this minimum. [#2482] - -### Removed -- `ConnectionInfo::get`. [#2487] - -[#2430]: https://github.com/actix/actix-web/pull/2430 -[#2468]: https://github.com/actix/actix-web/pull/2468 -[#2480]: https://github.com/actix/actix-web/pull/2480 -[#2482]: https://github.com/actix/actix-web/pull/2482 -[#2484]: https://github.com/actix/actix-web/pull/2484 -[#2485]: https://github.com/actix/actix-web/pull/2485 -[#2487]: https://github.com/actix/actix-web/pull/2487 -[#2491]: https://github.com/actix/actix-web/pull/2491 -[#2492]: https://github.com/actix/actix-web/pull/2492 -[#2493]: https://github.com/actix/actix-web/pull/2493 -[#2499]: https://github.com/actix/actix-web/pull/2499 - - -## 4.0.0-beta.13 - 2021-11-30 -### Changed -- Update `actix-tls` to `3.0.0-rc.1`. [#2474] - -[#2474]: https://github.com/actix/actix-web/pull/2474 - - -## 4.0.0-beta.12 - 2021-11-22 -### Changed -- Compress middleware's response type is now `AnyBody>`. [#2448] - -### Fixed -- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] - -### Removed -- `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] - -[#2446]: https://github.com/actix/actix-web/pull/2446 -[#2448]: https://github.com/actix/actix-web/pull/2448 - - -## 4.0.0-beta.11 - 2021-11-15 -### Added -- Re-export `dev::ServerHandle` from `actix-server`. [#2442] - -### Changed -- `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] -- Update `actix-server` to `2.0.0-beta.9`. [#2442] - -[#2423]: https://github.com/actix/actix-web/pull/2423 -[#2442]: https://github.com/actix/actix-web/pull/2442 - - -## 4.0.0-beta.10 - 2021-10-20 -### Added -- Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] -- `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] - -### Changed -- Associated type `FromRequest::Config` was removed. [#2233] -- Inner field made private on `web::Payload`. [#2384] -- `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] -- Updated rustls to v0.20. [#2414] -- Minimum supported Rust version (MSRV) is now 1.52. - -### Removed -- Useless `ServiceResponse::checked_expr` method. [#2401] - -[#2233]: https://github.com/actix/actix-web/pull/2233 -[#2362]: https://github.com/actix/actix-web/pull/2362 -[#2384]: https://github.com/actix/actix-web/pull/2384 -[#2401]: https://github.com/actix/actix-web/pull/2401 -[#2403]: https://github.com/actix/actix-web/pull/2403 -[#2409]: https://github.com/actix/actix-web/pull/2409 -[#2414]: https://github.com/actix/actix-web/pull/2414 - - -## 4.0.0-beta.9 - 2021-09-09 -### Added -- Re-export actix-service `ServiceFactory` in `dev` module. [#2325] - -### Changed -- Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] -- Move `BaseHttpResponse` to `dev::Response`. [#2379] -- Enable `TestRequest::param` to accept more than just static strings. [#2172] -- Minimum supported Rust version (MSRV) is now 1.51. - -### Fixed -- Fix quality parse error in Accept-Encoding header. [#2344] -- Re-export correct type at `web::HttpResponse`. [#2379] - -[#2172]: https://github.com/actix/actix-web/pull/2172 -[#2325]: https://github.com/actix/actix-web/pull/2325 -[#2344]: https://github.com/actix/actix-web/pull/2344 -[#2379]: https://github.com/actix/actix-web/pull/2379 - - -## 4.0.0-beta.8 - 2021-06-26 -### Added -- Add `ServiceRequest::parts_mut`. [#2177] -- Add extractors for `Uri` and `Method`. [#2263] -- Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] -- Add `Route::service` for using hand-written services as handlers. [#2262] - -### Changed -- Change compression algorithm features flags. [#2250] -- Deprecate `App::data` and `App::data_factory`. [#2271] -- Smarter extraction of `ConnectionInfo` parts. [#2282] - -### Fixed -- Scope and Resource middleware can access data items set on their own layer. [#2288] - -[#2177]: https://github.com/actix/actix-web/pull/2177 -[#2250]: https://github.com/actix/actix-web/pull/2250 -[#2271]: https://github.com/actix/actix-web/pull/2271 -[#2262]: https://github.com/actix/actix-web/pull/2262 -[#2263]: https://github.com/actix/actix-web/pull/2263 -[#2282]: https://github.com/actix/actix-web/pull/2282 -[#2288]: https://github.com/actix/actix-web/pull/2288 - - -## 4.0.0-beta.7 - 2021-06-17 -### Added -- `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] - -### Changed -- Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -[#2162]: (https://github.com/actix/actix-web/pull/2162) -- `ServiceResponse::error_response` now uses body type of `Body`. [#2201] -- `ServiceResponse::checked_expr` now returns a `Result`. [#2201] -- Update `language-tags` to `0.3`. -- `ServiceResponse::take_body`. [#2201] -- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] -- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] -- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] -- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] - -### Removed -- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] - -[#2200]: https://github.com/actix/actix-web/pull/2200 -[#2201]: https://github.com/actix/actix-web/pull/2201 -[#2253]: https://github.com/actix/actix-web/pull/2253 -[#2246]: https://github.com/actix/actix-web/pull/2246 - - -## 4.0.0-beta.6 - 2021-04-17 -### Added -- `HttpResponse` and `HttpResponseBuilder` structs. [#2065] - -### Changed -- Most error types are now marked `#[non_exhaustive]`. [#2148] -- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. - -[#2065]: https://github.com/actix/actix-web/pull/2065 -[#2148]: https://github.com/actix/actix-web/pull/2148 - - -## 4.0.0-beta.5 - 2021-04-02 -### Added -- `Header` extractor for extracting common HTTP headers in handlers. [#2094] -- Added `TestServer::client_headers` method. [#2097] - -### Fixed -- Double ampersand in Logger format is escaped correctly. [#2067] - -### Changed -- `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed - instead of skipping. (Only the first error is kept when multiple error occur) [#2093] - -### Removed -- The `client` mod was removed. Clients should now use `awc` directly. - [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) -- Integration testing was moved to new `actix-test` crate. Namely these items from the `test` - module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] - -[#2067]: https://github.com/actix/actix-web/pull/2067 -[#2093]: https://github.com/actix/actix-web/pull/2093 -[#2094]: https://github.com/actix/actix-web/pull/2094 -[#2097]: https://github.com/actix/actix-web/pull/2097 -[#2112]: https://github.com/actix/actix-web/pull/2112 - - -## 4.0.0-beta.4 - 2021-03-09 -### Changed -- Feature `cookies` is now optional and enabled by default. [#1981] -- `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default - behaviour of the `web::Json` extractor. [#2010] - -[#1981]: https://github.com/actix/actix-web/pull/1981 -[#2010]: https://github.com/actix/actix-web/pull/2010 - - -## 4.0.0-beta.3 - 2021-02-10 -- Update `actix-web-codegen` to `0.5.0-beta.1`. - - -## 4.0.0-beta.2 - 2021-02-10 -### Added -- The method `Either, web::Form>::into_inner()` which returns the inner type for - whichever variant was created. Also works for `Either, web::Json>`. [#1894] -- Add `services!` macro for helping register multiple services to `App`. [#1933] -- Enable registering a vec of services of the same type to `App` [#1933] - -### Changed -- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. - Making it simpler and more performant. [#1891] -- `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] -- `ServiceRequest::from_request` can no longer fail. [#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` provide `&Service` in closure - argument. [#1905] -- `web::block` no longer requires the output is a Result. [#1957] - -### Fixed -- Multiple calls to `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 -[#1933]: https://github.com/actix/actix-web/pull/1933 -[#1957]: https://github.com/actix/actix-web/pull/1957 - - -## 4.0.0-beta.1 - 2021-01-07 -### Added -- `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and - `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] - -### Changed -- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] -- Bumped `rand` to `0.8`. -- Update `rust-tls` to `0.19`. [#1813] -- Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] -- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration - guide for implications. [#1875] -- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] -- MSRV is now 1.46.0. - -### Fixed -- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] - -### Removed -- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now - exposed directly by the `middleware` module. -- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported - from `actix_web::error` module. [#1878] - -[#1812]: https://github.com/actix/actix-web/pull/1812 -[#1813]: https://github.com/actix/actix-web/pull/1813 -[#1852]: https://github.com/actix/actix-web/pull/1852 -[#1865]: https://github.com/actix/actix-web/pull/1865 -[#1875]: https://github.com/actix/actix-web/pull/1875 -[#1878]: https://github.com/actix/actix-web/pull/1878 - - -## 3.3.3 - 2021-12-18 -### Changed -- Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] - -[#2529]: https://github.com/actix/actix-web/pull/2529 - - -## 3.3.2 - 2020-12-01 -### Fixed -- Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] -- Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] -- Increase minimum `socket2` version. [#1803] - -[#1762]: https://github.com/actix/actix-web/pull/1762 -[#1798]: https://github.com/actix/actix-web/pull/1798 -[#1803]: https://github.com/actix/actix-web/pull/1803 - - -## 3.3.1 - 2020-11-29 -- Ensure `actix-http` dependency uses same `serde_urlencoded`. - - -## 3.3.0 - 2020-11-25 -### Added -- Add `Either` extractor helper. [#1788] - -### Changed -- Upgrade `serde_urlencoded` to `0.7`. [#1773] - -[#1773]: https://github.com/actix/actix-web/pull/1773 -[#1788]: https://github.com/actix/actix-web/pull/1788 - - -## 3.2.0 - 2020-10-30 -### Added -- Implement `exclude_regex` for Logger middleware. [#1723] -- Add request-local data extractor `web::ReqData`. [#1748] -- Add ability to register closure for request middleware logging. [#1749] -- Add `app_data` to `ServiceConfig`. [#1757] -- Expose `on_connect` for access to the connection stream before request is handled. [#1754] - -### Changed -- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. -- Print non-configured `Data` type when attempting extraction. [#1743] -- Re-export bytes::Buf{Mut} in web module. [#1750] -- Upgrade `pin-project` to `1.0`. - -[#1723]: https://github.com/actix/actix-web/pull/1723 -[#1743]: https://github.com/actix/actix-web/pull/1743 -[#1748]: https://github.com/actix/actix-web/pull/1748 -[#1750]: https://github.com/actix/actix-web/pull/1750 -[#1754]: https://github.com/actix/actix-web/pull/1754 -[#1749]: https://github.com/actix/actix-web/pull/1749 - - -## 3.1.0 - 2020-09-29 -### Changed -- Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` - to retain any trailing slashes. [#1695] -- Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` - via `web::Data::from` [#1710] - -### Fixed -- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] - -[#1695]: https://github.com/actix/actix-web/pull/1695 -[#1708]: https://github.com/actix/actix-web/pull/1708 -[#1710]: https://github.com/actix/actix-web/pull/1710 - - -## 3.0.2 - 2020-09-15 -### Fixed -- `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] - -[#1678]: https://github.com/actix/actix-web/pull/1678 - - -## 3.0.1 - 2020-09-13 -### Changed -- `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] - -[#1673]: https://github.com/actix/actix-web/pull/1673 - - -## 3.0.0 - 2020-09-11 -- No significant changes from `3.0.0-beta.4`. - - -## 3.0.0-beta.4 - 2020-09-09 -### Added -- `middleware::NormalizePath` now has configurable behavior for either always having a trailing - slash, or as the new addition, always trimming trailing slashes. [#1639] - -### Changed -- Update actix-codec and actix-utils dependencies. [#1634] -- `FormConfig` and `JsonConfig` configurations are now also considered when set - using `App::data`. [#1641] -- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] -- `HttpServer::maxconnrate` is renamed to the more expressive - `HttpServer::max_connection_rate`. [#1655] - -[#1639]: https://github.com/actix/actix-web/pull/1639 -[#1641]: https://github.com/actix/actix-web/pull/1641 -[#1634]: https://github.com/actix/actix-web/pull/1634 -[#1655]: https://github.com/actix/actix-web/pull/1655 - -## 3.0.0-beta.3 - 2020-08-17 -### Changed -- Update `rustls` to 0.18 - - -## 3.0.0-beta.2 - 2020-08-17 -### Changed -- `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set - using `App::data`. [#1610] -- `web::Path` now has a public representation: `web::Path(pub T)` that enables - destructuring. [#1594] -- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to - access `HttpRequest` which already allows this. [#1618] -- Re-export all error types from `awc`. [#1621] -- MSRV is now 1.42.0. - -### Fixed -- Memory leak of app data in pooled requests. [#1609] - -[#1594]: https://github.com/actix/actix-web/pull/1594 -[#1609]: https://github.com/actix/actix-web/pull/1609 -[#1610]: https://github.com/actix/actix-web/pull/1610 -[#1618]: https://github.com/actix/actix-web/pull/1618 -[#1621]: https://github.com/actix/actix-web/pull/1621 - - -## 3.0.0-beta.1 - 2020-07-13 -### Added -- Re-export `actix_rt::main` as `actix_web::main`. -- `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched - resource pattern. -- `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. - -### Changed -- Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] -- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. -- MSRV is now 1.41.1 - -### Fixed -- `NormalizePath` improved consistency when path needs slashes added _and_ removed. - - -## 3.0.0-alpha.3 - 2020-05-21 -### Added -- Add option to create `Data` from `Arc` [#1509] - -### Changed -- Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] -- Fix audit issue logging by default peer address [#1485] -- Bump minimum supported Rust version to 1.40 -- Replace deprecated `net2` crate with `socket2` - -[#1485]: https://github.com/actix/actix-web/pull/1485 -[#1509]: https://github.com/actix/actix-web/pull/1509 - -## [3.0.0-alpha.2] - 2020-05-08 - -### Changed - -- `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] -- Implement `std::error::Error` for our custom errors [#1422] -- NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] -- Remove the `failure` feature and support. - -[#1422]: https://github.com/actix/actix-web/pull/1422 -[#1433]: https://github.com/actix/actix-web/pull/1433 -[#1452]: https://github.com/actix/actix-web/pull/1452 -[#1486]: https://github.com/actix/actix-web/pull/1486 - - -## [3.0.0-alpha.1] - 2020-03-11 - -### Added - -- Add helper function for creating routes with `TRACE` method guard `web::trace()` -- Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. - -### Changed - -- Use `sha-1` crate instead of unmaintained `sha1` crate -- Skip empty chunks when returning response from a `Stream` [#1308] -- Update the `time` dependency to 0.2.7 -- Update `actix-tls` dependency to 2.0.0-alpha.1 -- Update `rustls` dependency to 0.17 - -[#1308]: https://github.com/actix/actix-web/pull/1308 - -## [2.0.0] - 2019-12-25 - -### Changed - -- Rename `HttpServer::start()` to `HttpServer::run()` - -- Allow to gracefully stop test server via `TestServer::stop()` - -- Allow to specify multi-patterns for resources - -## [2.0.0-rc] - 2019-12-20 - -### Changed - -- Move `BodyEncoding` to `dev` module #1220 - -- Allow to set `peer_addr` for TestRequest #1074 - -- Make web::Data deref to Arc #1214 - -- Rename `App::register_data()` to `App::app_data()` - -- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` - -### Fixed - -- Fix `AppConfig::secure()` is always false. #1202 - - -## [2.0.0-alpha.6] - 2019-12-15 - -### Fixed - -- Fixed compilation with default features off - -## [2.0.0-alpha.5] - 2019-12-13 - -### Added - -- Add test server, `test::start()` and `test::start_with()` - -## [2.0.0-alpha.4] - 2019-12-08 - -### Deleted - -- Delete HttpServer::run(), it is not useful with async/await - -## [2.0.0-alpha.3] - 2019-12-07 - -### Changed - -- Migrate to tokio 0.2 - - -## [2.0.0-alpha.1] - 2019-11-22 - -### Changed - -- Migrated to `std::future` - -- Remove implementation of `Responder` for `()`. (#1167) - - -## [1.0.9] - 2019-11-14 - -### Added - -- Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) - -### Changed - -- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) - - -## [1.0.8] - 2019-09-25 - -### Added - -- Add `Scope::register_data` and `Resource::register_data` methods, parallel to - `App::register_data`. - -- Add `middleware::Condition` that conditionally enables another middleware - -- Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` - -- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, - which is useful for example with systemd. - -### Changed - -- Make UrlEncodedError::Overflow more informative - -- Use actix-testing for testing utils - - -## [1.0.7] - 2019-08-29 - -### Fixed - -- Request Extensions leak #1062 - - -## [1.0.6] - 2019-08-28 - -### Added - -- Re-implement Host predicate (#989) - -- Form implements Responder, returning a `application/x-www-form-urlencoded` response - -- Add `into_inner` to `Data` - -- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set - the header in test requests. - -### Changed - -- `Query` payload made `pub`. Allows user to pattern-match the payload. - -- Enable `rust-tls` feature for client #1045 - -- Update serde_urlencoded to 0.6.1 - -- Update url to 2.1 - - -## [1.0.5] - 2019-07-18 - -### Added - -- Unix domain sockets (HttpServer::bind_uds) #92 - -- Actix now logs errors resulting in "internal server error" responses always, with the `error` - logging level - -### Fixed - -- Restored logging of errors through the `Logger` middleware - - -## [1.0.4] - 2019-07-17 - -### Added - -- Add `Responder` impl for `(T, StatusCode) where T: Responder` - -- Allow to access app's resource map via - `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. - -### Changed - -- Upgrade `rand` dependency version to 0.7 - - -## [1.0.3] - 2019-06-28 - -### Added - -- Support asynchronous data factories #850 - -### Changed - -- Use `encoding_rs` crate instead of unmaintained `encoding` crate - - -## [1.0.2] - 2019-06-17 - -### Changed - -- Move cors middleware to `actix-cors` crate. - -- Move identity middleware to `actix-identity` crate. - - -## [1.0.1] - 2019-06-17 - -### Added - -- Add support for PathConfig #903 - -- Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. - -### Changed - -- Move cors middleware to `actix-cors` crate. - -- Move identity middleware to `actix-identity` crate. - -- Disable default feature `secure-cookies`. - -- Allow to test an app that uses async actors #897 - -- Re-apply patch from #637 #894 - -### Fixed - -- HttpRequest::url_for is broken with nested scopes #915 - - -## [1.0.0] - 2019-06-05 - -### Added - -- Add `Scope::configure()` method. - -- Add `ServiceRequest::set_payload()` method. - -- Add `test::TestRequest::set_json()` convenience method to automatically - serialize data and set header in test requests. - -- Add macros for head, options, trace, connect and patch http methods - -### Changed - -- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 - -### Fixed - -- Fix Logger request time format, and use rfc3339. #867 - -- Clear http requests pool on app service drop #860 - - -## [1.0.0-rc] - 2019-05-18 - -### Added - -- Add `Query::from_query()` to extract parameters from a query string. #846 -- `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. - -### Changed - -- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. - -### Fixed - -- Codegen with parameters in the path only resolves the first registered endpoint #841 - - -## [1.0.0-beta.4] - 2019-05-12 - -### Added - -- Allow to set/override app data on scope level - -### Changed - -- `App::configure` take an `FnOnce` instead of `Fn` -- Upgrade actix-net crates - - -## [1.0.0-beta.3] - 2019-05-04 - -### Added - -- Add helper function for executing futures `test::block_fn()` - -### Changed - -- Extractor configuration could be registered with `App::data()` - or with `Resource::data()` #775 - -- Route data is unified with app data, `Route::data()` moved to resource - level to `Resource::data()` - -- CORS handling without headers #702 - -- Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. - -### Fixed - -- Fix `NormalizePath` middleware impl #806 - -### Deleted - -- `App::data_factory()` is deleted. - - -## [1.0.0-beta.2] - 2019-04-24 - -### Added - -- Add raw services support via `web::service()` - -- Add helper functions for reading response body `test::read_body()` - -- Add support for `remainder match` (i.e "/path/{tail}*") - -- Extend `Responder` trait, allow to override status code and headers. - -- Store visit and login timestamp in the identity cookie #502 - -### Changed - -- `.to_async()` handler can return `Responder` type #792 - -### Fixed - -- Fix async web::Data factory handling - - -## [1.0.0-beta.1] - 2019-04-20 - -### Added - -- Add helper functions for reading test response body, - `test::read_response()` and test::read_response_json()` - -- Add `.peer_addr()` #744 - -- Add `NormalizePath` middleware - -### Changed - -- Rename `RouterConfig` to `ServiceConfig` - -- Rename `test::call_success` to `test::call_service` - -- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. - -- `CookieIdentityPolicy::max_age()` accepts value in seconds - -### Fixed - -- Fixed `TestRequest::app_data()` - - -## [1.0.0-alpha.6] - 2019-04-14 - -### Changed - -- Allow using any service as default service. - -- Remove generic type for request payload, always use default. - -- Removed `Decompress` middleware. Bytes, String, Json, Form extractors - automatically decompress payload. - -- Make extractor config type explicit. Add `FromRequest::Config` associated type. - - -## [1.0.0-alpha.5] - 2019-04-12 - -### Added - -- Added async io `TestBuffer` for testing. - -### Deleted - -- Removed native-tls support - - -## [1.0.0-alpha.4] - 2019-04-08 - -### Added - -- `App::configure()` allow to offload app configuration to different methods - -- Added `URLPath` option for logger - -- Added `ServiceRequest::app_data()`, returns `Data` - -- Added `ServiceFromRequest::app_data()`, returns `Data` - -### Changed - -- `FromRequest` trait refactoring - -- Move multipart support to actix-multipart crate - -### Fixed - -- Fix body propagation in Response::from_error. #760 - - -## [1.0.0-alpha.3] - 2019-04-02 - -### Changed - -- Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` - -- Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` - -- Removed `Deref` impls - -### Removed - -- Removed unused `actix_web::web::md()` - - -## [1.0.0-alpha.2] - 2019-03-29 - -### Added - -- Rustls support - -### Changed - -- Use forked cookie - -- Multipart::Field renamed to MultipartField - -## [1.0.0-alpha.1] - 2019-03-28 - -### Changed - -- Complete architecture re-design. - -- Return 405 response if no matching route found within resource #538 +Actix Web changelog [is now here →](./actix-web/CHANGES.md). diff --git a/Cargo.toml b/Cargo.toml index 9610539de..7eed68e54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,6 @@ -[package] -name = "actix-web" -version = "4.0.0-rc.1" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] -description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" -keywords = ["actix", "http", "web", "framework", "async"] -categories = [ - "network-programming", - "asynchronous", - "web-programming::http-server", - "web-programming::websocket" -] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -license = "MIT OR Apache-2.0" -edition = "2018" - -[package.metadata.docs.rs] -# features that docs.rs will build with -features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] -rustdoc-args = ["--cfg", "docsrs"] - -[lib] -name = "actix_web" -path = "src/lib.rs" - [workspace] resolver = "2" members = [ - ".", "actix-files", "actix-http-test", "actix-http", @@ -39,93 +9,10 @@ members = [ "actix-test", "actix-web-actors", "actix-web-codegen", + "actix-web", "awc", ] -[features] -default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] - -# Brotli algorithm content-encoding support -compress-brotli = ["actix-http/compress-brotli", "__compress"] -# Gzip and deflate algorithms content-encoding support -compress-gzip = ["actix-http/compress-gzip", "__compress"] -# Zstd algorithm content-encoding support -compress-zstd = ["actix-http/compress-zstd", "__compress"] - -# support for cookies -cookies = ["cookie"] - -# secure cookies feature -secure-cookies = ["cookie/secure"] - -# openssl -openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] - -# rustls -rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] - -# Internal (PRIVATE!) features used to aid testing and checking feature status. -# Don't rely on these whatsoever. They may disappear at anytime. -__compress = [] - -# io-uring feature only avaiable for Linux OSes. -experimental-io-uring = ["actix-server/io-uring"] - -[dependencies] -actix-codec = "0.4.1" -actix-macros = "0.2.3" -actix-rt = "2.6" -actix-server = "2" -actix-service = "2.0.0" -actix-utils = "3.0.0" -actix-tls = { version = "3.0.0", default-features = false, optional = true } - -actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } -actix-router = "0.5.0-rc.3" -actix-web-codegen = "0.5.0-rc.2" - -ahash = "0.7" -bytes = "1" -cfg-if = "1" -cookie = { version = "0.16", features = ["percent-encode"], optional = true } -derive_more = "0.99.5" -encoding_rs = "0.8" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } -itoa = "1" -language-tags = "0.3" -once_cell = "1.5" -log = "0.4" -mime = "0.3" -pin-project-lite = "0.2.7" -regex = "1.4" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_urlencoded = "0.7" -smallvec = "1.6.1" -socket2 = "0.4.0" -time = { version = "0.3", default-features = false, features = ["formatting"] } -url = "2.1" - -[dev-dependencies] -actix-files = "0.6.0-beta.16" -actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.20", features = ["openssl"] } - -brotli = "3.3.3" -const-str = "0.3" -criterion = { version = "0.3", features = ["html_reports"] } -env_logger = "0.9" -flate2 = "1.0.13" -futures-util = { version = "0.3.7", default-features = false, features = ["std"] } -rand = "0.8" -rcgen = "0.8" -rustls-pemfile = "0.2" -static_assertions = "1" -tls-openssl = { package = "openssl", version = "0.10.9" } -tls-rustls = { package = "rustls", version = "0.20.0" } -zstd = "0.9" - [profile.dev] # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. debug = 0 @@ -155,35 +42,3 @@ awc = { path = "awc" } # actix-utils = { path = "../actix-net/actix-utils" } # actix-tls = { path = "../actix-net/actix-tls" } # actix-server = { path = "../actix-net/actix-server" } - -[[test]] -name = "test_server" -required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] - -[[test]] -name = "compression" -required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] - -[[example]] -name = "basic" -required-features = ["compress-gzip"] - -[[example]] -name = "uds" -required-features = ["compress-gzip"] - -[[example]] -name = "on-connect" -required-features = [] - -[[bench]] -name = "server" -harness = false - -[[bench]] -name = "service" -harness = false - -[[bench]] -name = "responder" -harness = false diff --git a/README.md b/README.md deleted file mode 100644 index f99a7be23..000000000 --- a/README.md +++ /dev/null @@ -1,105 +0,0 @@ -

-

Actix Web

-

- Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust -

-

- -[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.1)](https://docs.rs/actix-web/4.0.0-rc.1) -![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) -![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.1) -
-[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) -![downloads](https://img.shields.io/crates/d/actix-web.svg) -[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) - -

-
- -## Features - -- Supports _HTTP/1.x_ and _HTTP/2_ -- Streaming and pipelining -- Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros -- Full [Tokio](https://tokio.rs) compatibility -- Keep-alive and slow requests handling -- Client/server [WebSockets](https://actix.rs/docs/websockets/) support -- Transparent content compression/decompression (br, gzip, deflate, zstd) -- Multipart streams -- Static assets -- SSL support using OpenSSL or Rustls -- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -- Includes an async [HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.54+ - -## Documentation - -- [Website & User Guide](https://actix.rs) -- [Examples Repository](https://github.com/actix/examples) -- [API Documentation](https://docs.rs/actix-web) -- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) - -## Example - -Dependencies: - -```toml -[dependencies] -actix-web = "4.0.0-rc.1" -``` - -Code: - -```rust -use actix_web::{get, web, App, HttpServer, Responder}; - -#[get("/{id}/{name}/index.html")] -async fn index(params: web::Path<(u32, String)>) -> impl Responder { - let (id, name) = params.into_inner(); - format!("Hello {}! id:{}", name, id) -} - -#[actix_web::main] // or #[tokio::main] -async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(index)) - .bind(("127.0.0.1", 8080))? - .run() - .await -} -``` - -### More examples - -- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) -- [Application State](https://github.com/actix/examples/tree/master/basics/state/) -- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) -- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) -- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) -- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) -- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) -- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) -- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) -- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) -- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) -- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) - -You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). - -## License - -This project is licensed under either of the following licenses, at your option: - -- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) - -## Code of Conduct - -Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. -The Actix team promises to intervene to uphold that code of conduct. diff --git a/README.md b/README.md new file mode 120000 index 000000000..16b750c17 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +actix-web/README.md \ No newline at end of file diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md new file mode 100644 index 000000000..a7e0a0309 --- /dev/null +++ b/actix-web/CHANGES.md @@ -0,0 +1,1032 @@ +# Changes + +## Unreleased - 2021-xx-xx + + +## 4.0.0-rc.1 - 2022-01-31 +### Changed +- Rename `HttpServer::{client_timeout => client_request_timeout}`. [#2611] +- Rename `HttpServer::{client_shutdown => client_disconnect_timeout}`. [#2611] + +### Removed +- `impl Future for HttpResponse`. [#2601] + +[#2601]: https://github.com/actix/actix-web/pull/2601 +[#2611]: https://github.com/actix/actix-web/pull/2611 + + +## 4.0.0-beta.21 - 2022-01-21 +### Added +- `HttpResponse::add_removal_cookie`. [#2586] +- `Logger::log_target`. [#2594] + +### Removed +- `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585] +- `HttpRequestBuilder::del_cookie`. [#2591] + +[#2585]: https://github.com/actix/actix-web/pull/2585 +[#2586]: https://github.com/actix/actix-web/pull/2586 +[#2591]: https://github.com/actix/actix-web/pull/2591 +[#2594]: https://github.com/actix/actix-web/pull/2594 + + +## 4.0.0-beta.20 - 2022-01-14 +### Added +- `GuardContext::header` [#2569] +- `ServiceConfig::configure` to allow easy nesting of configuration functions. [#1988] + +### Changed +- `HttpResponse` can now be used as a `Responder` with any body type. [#2567] +- `Result` extractor wrapper can now convert error types. [#2581] +- Associated types in `FromRequest` impl for `Option` and `Result` has changed. [#2581] +- Maximum number of handler extractors has increased to 12. [#2582] +- Removed bound `::Error: Debug` in test utility functions in order to support returning opaque apps. [#2584] + +[#1988]: https://github.com/actix/actix-web/pull/1988 +[#2567]: https://github.com/actix/actix-web/pull/2567 +[#2569]: https://github.com/actix/actix-web/pull/2569 +[#2581]: https://github.com/actix/actix-web/pull/2581 +[#2582]: https://github.com/actix/actix-web/pull/2582 +[#2584]: https://github.com/actix/actix-web/pull/2584 + + +## 4.0.0-beta.19 - 2022-01-04 +### Added +- `impl Hash` for `http::header::Encoding`. [#2501] +- `AcceptEncoding::negotiate()`. [#2501] + +### Changed +- `AcceptEncoding::preference` now returns `Option>`. [#2501] +- Rename methods `BodyEncoding::{encoding => encode_with, get_encoding => preferred_encoding}`. [#2501] +- `http::header::Encoding` now only represents `Content-Encoding` types. [#2501] + +### Fixed +- Auto-negotiation of content encoding is more fault-tolerant when using the `Compress` middleware. [#2501] + +### Removed +- `Compress::new`; restricting compression algorithm is done through feature flags. [#2501] +- `BodyEncoding` trait; signalling content encoding is now only done via the `Content-Encoding` header. [#2565] + +[#2501]: https://github.com/actix/actix-web/pull/2501 +[#2565]: https://github.com/actix/actix-web/pull/2565 + + +## 4.0.0-beta.18 - 2021-12-29 +### Changed +- Update `cookie` dependency (re-exported) to `0.16`. [#2555] +- Minimum supported Rust version (MSRV) is now 1.54. + +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[#2555]: https://github.com/actix/actix-web/pull/2555 +[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + + +## 4.0.0-beta.17 - 2021-12-29 +### Added +- `guard::GuardContext` for use with the `Guard` trait. [#2552] +- `ServiceRequest::guard_ctx` for obtaining a guard context. [#2552] + +### Changed +- `Guard` trait now receives a `&GuardContext`. [#2552] +- `guard::fn_guard` functions now receives a `&GuardContext`. [#2552] +- Some guards now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552] +- The `Not` guard is now generic over the type of guard it wraps. [#2552] + +### Fixed +- Rename `ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554] +- `ConnectionInfo::peer_addr` will not return the port number. [#2554] +- `ConnectionInfo::realip_remote_addr` will not return the port number if sourcing the IP from the peer's socket address. [#2554] + +[#2552]: https://github.com/actix/actix-web/pull/2552 +[#2554]: https://github.com/actix/actix-web/pull/2554 + + +## 4.0.0-beta.16 - 2021-12-27 +### Changed +- No longer require `Scope` service body type to be boxed. [#2523] +- No longer require `Resource` service body type to be boxed. [#2526] + +[#2523]: https://github.com/actix/actix-web/pull/2523 +[#2526]: https://github.com/actix/actix-web/pull/2526 + + +## 4.0.0-beta.15 - 2021-12-17 +### Added +- Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510] +- Implement `Debug` for `DefaultHeaders`. [#2510] + +### Changed +- Align `DefaultHeader` method terminology, deprecating previous methods. [#2510] +- Response service types in `ErrorHandlers` middleware now use `ServiceResponse>` to allow changing the body type. [#2515] +- Both variants in `ErrorHandlerResponse` now use `ServiceResponse>`. [#2515] +- Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518] +- Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518] +- Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518] +- Relax body type and error bounds on test utilities. [#2518] + +### Removed +- Top-level `EitherExtractError` export. [#2510] +- Conversion implementations for `either` crate. [#2516] +- `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] + +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2515]: https://github.com/actix/actix-web/pull/2515 +[#2516]: https://github.com/actix/actix-web/pull/2516 +[#2518]: https://github.com/actix/actix-web/pull/2518 + + +## 4.0.0-beta.14 - 2021-12-11 +### Added +- Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] +- `AcceptEncoding` typed header. [#2482] +- `Range` typed header. [#2485] +- `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] +- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] +- `HttpRequest::{req_data,req_data_mut}`. [#2487] +- `ServiceResponse::into_parts`. [#2499] + +### Changed +- Rename `Accept::{mime_precedence => ranked}`. [#2480] +- Rename `Accept::{mime_preference => preference}`. [#2480] +- Un-deprecate `App::data_factory`. [#2484] +- `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] +- Remove `B` (body) type parameter on `App`. [#2493] +- Add `B` (body) type parameter on `Scope`. [#2492] +- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] + +### Fixed +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] +- Typed headers containing lists that require one or more items now enforce this minimum. [#2482] + +### Removed +- `ConnectionInfo::get`. [#2487] + +[#2430]: https://github.com/actix/actix-web/pull/2430 +[#2468]: https://github.com/actix/actix-web/pull/2468 +[#2480]: https://github.com/actix/actix-web/pull/2480 +[#2482]: https://github.com/actix/actix-web/pull/2482 +[#2484]: https://github.com/actix/actix-web/pull/2484 +[#2485]: https://github.com/actix/actix-web/pull/2485 +[#2487]: https://github.com/actix/actix-web/pull/2487 +[#2491]: https://github.com/actix/actix-web/pull/2491 +[#2492]: https://github.com/actix/actix-web/pull/2492 +[#2493]: https://github.com/actix/actix-web/pull/2493 +[#2499]: https://github.com/actix/actix-web/pull/2499 + + +## 4.0.0-beta.13 - 2021-11-30 +### Changed +- Update `actix-tls` to `3.0.0-rc.1`. [#2474] + +[#2474]: https://github.com/actix/actix-web/pull/2474 + + +## 4.0.0-beta.12 - 2021-11-22 +### Changed +- Compress middleware's response type is now `AnyBody>`. [#2448] + +### Fixed +- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] + +### Removed +- `dev::ResponseBody` re-export; is function is replaced by the new `dev::AnyBody` enum. [#2446] + +[#2446]: https://github.com/actix/actix-web/pull/2446 +[#2448]: https://github.com/actix/actix-web/pull/2448 + + +## 4.0.0-beta.11 - 2021-11-15 +### Added +- Re-export `dev::ServerHandle` from `actix-server`. [#2442] + +### Changed +- `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] +- Update `actix-server` to `2.0.0-beta.9`. [#2442] + +[#2423]: https://github.com/actix/actix-web/pull/2423 +[#2442]: https://github.com/actix/actix-web/pull/2442 + + +## 4.0.0-beta.10 - 2021-10-20 +### Added +- Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] +- `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] + +### Changed +- Associated type `FromRequest::Config` was removed. [#2233] +- Inner field made private on `web::Payload`. [#2384] +- `Data::into_inner` and `Data::get_ref` no longer requires `T: Sized`. [#2403] +- Updated rustls to v0.20. [#2414] +- Minimum supported Rust version (MSRV) is now 1.52. + +### Removed +- Useless `ServiceResponse::checked_expr` method. [#2401] + +[#2233]: https://github.com/actix/actix-web/pull/2233 +[#2362]: https://github.com/actix/actix-web/pull/2362 +[#2384]: https://github.com/actix/actix-web/pull/2384 +[#2401]: https://github.com/actix/actix-web/pull/2401 +[#2403]: https://github.com/actix/actix-web/pull/2403 +[#2409]: https://github.com/actix/actix-web/pull/2409 +[#2414]: https://github.com/actix/actix-web/pull/2414 + + +## 4.0.0-beta.9 - 2021-09-09 +### Added +- Re-export actix-service `ServiceFactory` in `dev` module. [#2325] + +### Changed +- Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344] +- Move `BaseHttpResponse` to `dev::Response`. [#2379] +- Enable `TestRequest::param` to accept more than just static strings. [#2172] +- Minimum supported Rust version (MSRV) is now 1.51. + +### Fixed +- Fix quality parse error in Accept-Encoding header. [#2344] +- Re-export correct type at `web::HttpResponse`. [#2379] + +[#2172]: https://github.com/actix/actix-web/pull/2172 +[#2325]: https://github.com/actix/actix-web/pull/2325 +[#2344]: https://github.com/actix/actix-web/pull/2344 +[#2379]: https://github.com/actix/actix-web/pull/2379 + + +## 4.0.0-beta.8 - 2021-06-26 +### Added +- Add `ServiceRequest::parts_mut`. [#2177] +- Add extractors for `Uri` and `Method`. [#2263] +- Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +- Add `Route::service` for using hand-written services as handlers. [#2262] + +### Changed +- Change compression algorithm features flags. [#2250] +- Deprecate `App::data` and `App::data_factory`. [#2271] +- Smarter extraction of `ConnectionInfo` parts. [#2282] + +### Fixed +- Scope and Resource middleware can access data items set on their own layer. [#2288] + +[#2177]: https://github.com/actix/actix-web/pull/2177 +[#2250]: https://github.com/actix/actix-web/pull/2250 +[#2271]: https://github.com/actix/actix-web/pull/2271 +[#2262]: https://github.com/actix/actix-web/pull/2262 +[#2263]: https://github.com/actix/actix-web/pull/2263 +[#2282]: https://github.com/actix/actix-web/pull/2282 +[#2288]: https://github.com/actix/actix-web/pull/2288 + + +## 4.0.0-beta.7 - 2021-06-17 +### Added +- `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] + +### Changed +- Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] +[#2162]: (https://github.com/actix/actix-web/pull/2162) +- `ServiceResponse::error_response` now uses body type of `Body`. [#2201] +- `ServiceResponse::checked_expr` now returns a `Result`. [#2201] +- Update `language-tags` to `0.3`. +- `ServiceResponse::take_body`. [#2201] +- `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +- All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] +- All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] + +### Removed +- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] + +[#2200]: https://github.com/actix/actix-web/pull/2200 +[#2201]: https://github.com/actix/actix-web/pull/2201 +[#2253]: https://github.com/actix/actix-web/pull/2253 +[#2246]: https://github.com/actix/actix-web/pull/2246 + + +## 4.0.0-beta.6 - 2021-04-17 +### Added +- `HttpResponse` and `HttpResponseBuilder` structs. [#2065] + +### Changed +- Most error types are now marked `#[non_exhaustive]`. [#2148] +- Methods on `ContentDisposition` that took `T: AsRef` now take `impl AsRef`. + +[#2065]: https://github.com/actix/actix-web/pull/2065 +[#2148]: https://github.com/actix/actix-web/pull/2148 + + +## 4.0.0-beta.5 - 2021-04-02 +### Added +- `Header` extractor for extracting common HTTP headers in handlers. [#2094] +- Added `TestServer::client_headers` method. [#2097] + +### Fixed +- Double ampersand in Logger format is escaped correctly. [#2067] + +### Changed +- `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed + instead of skipping. (Only the first error is kept when multiple error occur) [#2093] + +### Removed +- The `client` mod was removed. Clients should now use `awc` directly. + [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) +- Integration testing was moved to new `actix-test` crate. Namely these items from the `test` + module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112] + +[#2067]: https://github.com/actix/actix-web/pull/2067 +[#2093]: https://github.com/actix/actix-web/pull/2093 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2097]: https://github.com/actix/actix-web/pull/2097 +[#2112]: https://github.com/actix/actix-web/pull/2112 + + +## 4.0.0-beta.4 - 2021-03-09 +### Changed +- Feature `cookies` is now optional and enabled by default. [#1981] +- `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the default + behaviour of the `web::Json` extractor. [#2010] + +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#2010]: https://github.com/actix/actix-web/pull/2010 + + +## 4.0.0-beta.3 - 2021-02-10 +- Update `actix-web-codegen` to `0.5.0-beta.1`. + + +## 4.0.0-beta.2 - 2021-02-10 +### Added +- The method `Either, web::Form>::into_inner()` which returns the inner type for + whichever variant was created. Also works for `Either, web::Json>`. [#1894] +- Add `services!` macro for helping register multiple services to `App`. [#1933] +- Enable registering a vec of services of the same type to `App` [#1933] + +### Changed +- Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. + Making it simpler and more performant. [#1891] +- `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] +- `ServiceRequest::from_request` can no longer fail. [#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` provide `&Service` in closure + argument. [#1905] +- `web::block` no longer requires the output is a Result. [#1957] + +### Fixed +- Multiple calls to `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 +[#1933]: https://github.com/actix/actix-web/pull/1933 +[#1957]: https://github.com/actix/actix-web/pull/1957 + + +## 4.0.0-beta.1 - 2021-01-07 +### Added +- `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and + `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] + +### Changed +- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +- Bumped `rand` to `0.8`. +- Update `rust-tls` to `0.19`. [#1813] +- Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +- The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration + guide for implications. [#1875] +- Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] +- MSRV is now 1.46.0. + +### Fixed +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + +### Removed +- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now + exposed directly by the `middleware` module. +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported + from `actix_web::error` module. [#1878] + +[#1812]: https://github.com/actix/actix-web/pull/1812 +[#1813]: https://github.com/actix/actix-web/pull/1813 +[#1852]: https://github.com/actix/actix-web/pull/1852 +[#1865]: https://github.com/actix/actix-web/pull/1865 +[#1875]: https://github.com/actix/actix-web/pull/1875 +[#1878]: https://github.com/actix/actix-web/pull/1878 + + +## 3.3.3 - 2021-12-18 +### Changed +- Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529] + +[#2529]: https://github.com/actix/actix-web/pull/2529 + + +## 3.3.2 - 2020-12-01 +### Fixed +- Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] +- Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] +- Increase minimum `socket2` version. [#1803] + +[#1762]: https://github.com/actix/actix-web/pull/1762 +[#1798]: https://github.com/actix/actix-web/pull/1798 +[#1803]: https://github.com/actix/actix-web/pull/1803 + + +## 3.3.1 - 2020-11-29 +- Ensure `actix-http` dependency uses same `serde_urlencoded`. + + +## 3.3.0 - 2020-11-25 +### Added +- Add `Either` extractor helper. [#1788] + +### Changed +- Upgrade `serde_urlencoded` to `0.7`. [#1773] + +[#1773]: https://github.com/actix/actix-web/pull/1773 +[#1788]: https://github.com/actix/actix-web/pull/1788 + + +## 3.2.0 - 2020-10-30 +### Added +- Implement `exclude_regex` for Logger middleware. [#1723] +- Add request-local data extractor `web::ReqData`. [#1748] +- Add ability to register closure for request middleware logging. [#1749] +- Add `app_data` to `ServiceConfig`. [#1757] +- Expose `on_connect` for access to the connection stream before request is handled. [#1754] + +### Changed +- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Print non-configured `Data` type when attempting extraction. [#1743] +- Re-export bytes::Buf{Mut} in web module. [#1750] +- Upgrade `pin-project` to `1.0`. + +[#1723]: https://github.com/actix/actix-web/pull/1723 +[#1743]: https://github.com/actix/actix-web/pull/1743 +[#1748]: https://github.com/actix/actix-web/pull/1748 +[#1750]: https://github.com/actix/actix-web/pull/1750 +[#1754]: https://github.com/actix/actix-web/pull/1754 +[#1749]: https://github.com/actix/actix-web/pull/1749 + + +## 3.1.0 - 2020-09-29 +### Changed +- Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` + to retain any trailing slashes. [#1695] +- Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` + via `web::Data::from` [#1710] + +### Fixed +- `ResourceMap` debug printing is no longer infinitely recursive. [#1708] + +[#1695]: https://github.com/actix/actix-web/pull/1695 +[#1708]: https://github.com/actix/actix-web/pull/1708 +[#1710]: https://github.com/actix/actix-web/pull/1710 + + +## 3.0.2 - 2020-09-15 +### Fixed +- `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678] + +[#1678]: https://github.com/actix/actix-web/pull/1678 + + +## 3.0.1 - 2020-09-13 +### Changed +- `middleware::normalize::TrailingSlash` enum is now accessible. [#1673] + +[#1673]: https://github.com/actix/actix-web/pull/1673 + + +## 3.0.0 - 2020-09-11 +- No significant changes from `3.0.0-beta.4`. + + +## 3.0.0-beta.4 - 2020-09-09 +### Added +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing + slash, or as the new addition, always trimming trailing slashes. [#1639] + +### Changed +- Update actix-codec and actix-utils dependencies. [#1634] +- `FormConfig` and `JsonConfig` configurations are now also considered when set + using `App::data`. [#1641] +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655] +- `HttpServer::maxconnrate` is renamed to the more expressive + `HttpServer::max_connection_rate`. [#1655] + +[#1639]: https://github.com/actix/actix-web/pull/1639 +[#1641]: https://github.com/actix/actix-web/pull/1641 +[#1634]: https://github.com/actix/actix-web/pull/1634 +[#1655]: https://github.com/actix/actix-web/pull/1655 + +## 3.0.0-beta.3 - 2020-08-17 +### Changed +- Update `rustls` to 0.18 + + +## 3.0.0-beta.2 - 2020-08-17 +### Changed +- `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set + using `App::data`. [#1610] +- `web::Path` now has a public representation: `web::Path(pub T)` that enables + destructuring. [#1594] +- `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to + access `HttpRequest` which already allows this. [#1618] +- Re-export all error types from `awc`. [#1621] +- MSRV is now 1.42.0. + +### Fixed +- Memory leak of app data in pooled requests. [#1609] + +[#1594]: https://github.com/actix/actix-web/pull/1594 +[#1609]: https://github.com/actix/actix-web/pull/1609 +[#1610]: https://github.com/actix/actix-web/pull/1610 +[#1618]: https://github.com/actix/actix-web/pull/1618 +[#1621]: https://github.com/actix/actix-web/pull/1621 + + +## 3.0.0-beta.1 - 2020-07-13 +### Added +- Re-export `actix_rt::main` as `actix_web::main`. +- `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched + resource pattern. +- `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. + +### Changed +- Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] +- Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency. +- MSRV is now 1.41.1 + +### Fixed +- `NormalizePath` improved consistency when path needs slashes added _and_ removed. + + +## 3.0.0-alpha.3 - 2020-05-21 +### Added +- Add option to create `Data` from `Arc` [#1509] + +### Changed +- Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] +- Fix audit issue logging by default peer address [#1485] +- Bump minimum supported Rust version to 1.40 +- Replace deprecated `net2` crate with `socket2` + +[#1485]: https://github.com/actix/actix-web/pull/1485 +[#1509]: https://github.com/actix/actix-web/pull/1509 + +## [3.0.0-alpha.2] - 2020-05-08 + +### Changed + +- `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] +- Implement `std::error::Error` for our custom errors [#1422] +- NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] +- Remove the `failure` feature and support. + +[#1422]: https://github.com/actix/actix-web/pull/1422 +[#1433]: https://github.com/actix/actix-web/pull/1433 +[#1452]: https://github.com/actix/actix-web/pull/1452 +[#1486]: https://github.com/actix/actix-web/pull/1486 + + +## [3.0.0-alpha.1] - 2020-03-11 + +### Added + +- Add helper function for creating routes with `TRACE` method guard `web::trace()` +- Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing. + +### Changed + +- Use `sha-1` crate instead of unmaintained `sha1` crate +- Skip empty chunks when returning response from a `Stream` [#1308] +- Update the `time` dependency to 0.2.7 +- Update `actix-tls` dependency to 2.0.0-alpha.1 +- Update `rustls` dependency to 0.17 + +[#1308]: https://github.com/actix/actix-web/pull/1308 + +## [2.0.0] - 2019-12-25 + +### Changed + +- Rename `HttpServer::start()` to `HttpServer::run()` + +- Allow to gracefully stop test server via `TestServer::stop()` + +- Allow to specify multi-patterns for resources + +## [2.0.0-rc] - 2019-12-20 + +### Changed + +- Move `BodyEncoding` to `dev` module #1220 + +- Allow to set `peer_addr` for TestRequest #1074 + +- Make web::Data deref to Arc #1214 + +- Rename `App::register_data()` to `App::app_data()` + +- `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` + +### Fixed + +- Fix `AppConfig::secure()` is always false. #1202 + + +## [2.0.0-alpha.6] - 2019-12-15 + +### Fixed + +- Fixed compilation with default features off + +## [2.0.0-alpha.5] - 2019-12-13 + +### Added + +- Add test server, `test::start()` and `test::start_with()` + +## [2.0.0-alpha.4] - 2019-12-08 + +### Deleted + +- Delete HttpServer::run(), it is not useful with async/await + +## [2.0.0-alpha.3] - 2019-12-07 + +### Changed + +- Migrate to tokio 0.2 + + +## [2.0.0-alpha.1] - 2019-11-22 + +### Changed + +- Migrated to `std::future` + +- Remove implementation of `Responder` for `()`. (#1167) + + +## [1.0.9] - 2019-11-14 + +### Added + +- Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) + +### Changed + +- Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) + + +## [1.0.8] - 2019-09-25 + +### Added + +- Add `Scope::register_data` and `Resource::register_data` methods, parallel to + `App::register_data`. + +- Add `middleware::Condition` that conditionally enables another middleware + +- Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` + +- Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, + which is useful for example with systemd. + +### Changed + +- Make UrlEncodedError::Overflow more informative + +- Use actix-testing for testing utils + + +## [1.0.7] - 2019-08-29 + +### Fixed + +- Request Extensions leak #1062 + + +## [1.0.6] - 2019-08-28 + +### Added + +- Re-implement Host predicate (#989) + +- Form implements Responder, returning a `application/x-www-form-urlencoded` response + +- Add `into_inner` to `Data` + +- Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set + the header in test requests. + +### Changed + +- `Query` payload made `pub`. Allows user to pattern-match the payload. + +- Enable `rust-tls` feature for client #1045 + +- Update serde_urlencoded to 0.6.1 + +- Update url to 2.1 + + +## [1.0.5] - 2019-07-18 + +### Added + +- Unix domain sockets (HttpServer::bind_uds) #92 + +- Actix now logs errors resulting in "internal server error" responses always, with the `error` + logging level + +### Fixed + +- Restored logging of errors through the `Logger` middleware + + +## [1.0.4] - 2019-07-17 + +### Added + +- Add `Responder` impl for `(T, StatusCode) where T: Responder` + +- Allow to access app's resource map via + `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. + +### Changed + +- Upgrade `rand` dependency version to 0.7 + + +## [1.0.3] - 2019-06-28 + +### Added + +- Support asynchronous data factories #850 + +### Changed + +- Use `encoding_rs` crate instead of unmaintained `encoding` crate + + +## [1.0.2] - 2019-06-17 + +### Changed + +- Move cors middleware to `actix-cors` crate. + +- Move identity middleware to `actix-identity` crate. + + +## [1.0.1] - 2019-06-17 + +### Added + +- Add support for PathConfig #903 + +- Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. + +### Changed + +- Move cors middleware to `actix-cors` crate. + +- Move identity middleware to `actix-identity` crate. + +- Disable default feature `secure-cookies`. + +- Allow to test an app that uses async actors #897 + +- Re-apply patch from #637 #894 + +### Fixed + +- HttpRequest::url_for is broken with nested scopes #915 + + +## [1.0.0] - 2019-06-05 + +### Added + +- Add `Scope::configure()` method. + +- Add `ServiceRequest::set_payload()` method. + +- Add `test::TestRequest::set_json()` convenience method to automatically + serialize data and set header in test requests. + +- Add macros for head, options, trace, connect and patch http methods + +### Changed + +- Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 + +### Fixed + +- Fix Logger request time format, and use rfc3339. #867 + +- Clear http requests pool on app service drop #860 + + +## [1.0.0-rc] - 2019-05-18 + +### Added + +- Add `Query::from_query()` to extract parameters from a query string. #846 +- `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. + +### Changed + +- `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. + +### Fixed + +- Codegen with parameters in the path only resolves the first registered endpoint #841 + + +## [1.0.0-beta.4] - 2019-05-12 + +### Added + +- Allow to set/override app data on scope level + +### Changed + +- `App::configure` take an `FnOnce` instead of `Fn` +- Upgrade actix-net crates + + +## [1.0.0-beta.3] - 2019-05-04 + +### Added + +- Add helper function for executing futures `test::block_fn()` + +### Changed + +- Extractor configuration could be registered with `App::data()` + or with `Resource::data()` #775 + +- Route data is unified with app data, `Route::data()` moved to resource + level to `Resource::data()` + +- CORS handling without headers #702 + +- Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types. + +### Fixed + +- Fix `NormalizePath` middleware impl #806 + +### Deleted + +- `App::data_factory()` is deleted. + + +## [1.0.0-beta.2] - 2019-04-24 + +### Added + +- Add raw services support via `web::service()` + +- Add helper functions for reading response body `test::read_body()` + +- Add support for `remainder match` (i.e "/path/{tail}*") + +- Extend `Responder` trait, allow to override status code and headers. + +- Store visit and login timestamp in the identity cookie #502 + +### Changed + +- `.to_async()` handler can return `Responder` type #792 + +### Fixed + +- Fix async web::Data factory handling + + +## [1.0.0-beta.1] - 2019-04-20 + +### Added + +- Add helper functions for reading test response body, + `test::read_response()` and test::read_response_json()` + +- Add `.peer_addr()` #744 + +- Add `NormalizePath` middleware + +### Changed + +- Rename `RouterConfig` to `ServiceConfig` + +- Rename `test::call_success` to `test::call_service` + +- Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. + +- `CookieIdentityPolicy::max_age()` accepts value in seconds + +### Fixed + +- Fixed `TestRequest::app_data()` + + +## [1.0.0-alpha.6] - 2019-04-14 + +### Changed + +- Allow using any service as default service. + +- Remove generic type for request payload, always use default. + +- Removed `Decompress` middleware. Bytes, String, Json, Form extractors + automatically decompress payload. + +- Make extractor config type explicit. Add `FromRequest::Config` associated type. + + +## [1.0.0-alpha.5] - 2019-04-12 + +### Added + +- Added async io `TestBuffer` for testing. + +### Deleted + +- Removed native-tls support + + +## [1.0.0-alpha.4] - 2019-04-08 + +### Added + +- `App::configure()` allow to offload app configuration to different methods + +- Added `URLPath` option for logger + +- Added `ServiceRequest::app_data()`, returns `Data` + +- Added `ServiceFromRequest::app_data()`, returns `Data` + +### Changed + +- `FromRequest` trait refactoring + +- Move multipart support to actix-multipart crate + +### Fixed + +- Fix body propagation in Response::from_error. #760 + + +## [1.0.0-alpha.3] - 2019-04-02 + +### Changed + +- Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` + +- Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` + +- Removed `Deref` impls + +### Removed + +- Removed unused `actix_web::web::md()` + + +## [1.0.0-alpha.2] - 2019-03-29 + +### Added + +- Rustls support + +### Changed + +- Use forked cookie + +- Multipart::Field renamed to MultipartField + +## [1.0.0-alpha.1] - 2019-03-28 + +### Changed + +- Complete architecture re-design. + +- Return 405 response if no matching route found within resource #538 diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml new file mode 100644 index 000000000..4e10dce18 --- /dev/null +++ b/actix-web/Cargo.toml @@ -0,0 +1,144 @@ +[package] +name = "actix-web" +version = "4.0.0-rc.1" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] +description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" +keywords = ["actix", "http", "web", "framework", "async"] +categories = [ + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket" +] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +license = "MIT OR Apache-2.0" +edition = "2018" + +[package.metadata.docs.rs] +# features that docs.rs will build with +features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] +rustdoc-args = ["--cfg", "docsrs"] + +[lib] +name = "actix_web" +path = "src/lib.rs" + +[features] +default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] + +# Brotli algorithm content-encoding support +compress-brotli = ["actix-http/compress-brotli", "__compress"] +# Gzip and deflate algorithms content-encoding support +compress-gzip = ["actix-http/compress-gzip", "__compress"] +# Zstd algorithm content-encoding support +compress-zstd = ["actix-http/compress-zstd", "__compress"] + +# support for cookies +cookies = ["cookie"] + +# secure cookies feature +secure-cookies = ["cookie/secure"] + +# openssl +openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] + +# rustls +rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] + +# Internal (PRIVATE!) features used to aid testing and checking feature status. +# Don't rely on these whatsoever. They may disappear at anytime. +__compress = [] + +# io-uring feature only avaiable for Linux OSes. +experimental-io-uring = ["actix-server/io-uring"] + +[dependencies] +actix-codec = "0.4.1" +actix-macros = "0.2.3" +actix-rt = "2.6" +actix-server = "2" +actix-service = "2.0.0" +actix-utils = "3.0.0" +actix-tls = { version = "3.0.0", default-features = false, optional = true } + +actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } +actix-router = "0.5.0-rc.3" +actix-web-codegen = "0.5.0-rc.2" + +ahash = "0.7" +bytes = "1" +cfg-if = "1" +cookie = { version = "0.16", features = ["percent-encode"], optional = true } +derive_more = "0.99.5" +encoding_rs = "0.8" +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } +itoa = "1" +language-tags = "0.3" +once_cell = "1.5" +log = "0.4" +mime = "0.3" +pin-project-lite = "0.2.7" +regex = "1.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_urlencoded = "0.7" +smallvec = "1.6.1" +socket2 = "0.4.0" +time = { version = "0.3", default-features = false, features = ["formatting"] } +url = "2.1" + +[dev-dependencies] +actix-files = "0.6.0-beta.16" +actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } +awc = { version = "3.0.0-beta.20", features = ["openssl"] } + +brotli = "3.3.3" +const-str = "0.3" +criterion = { version = "0.3", features = ["html_reports"] } +env_logger = "0.9" +flate2 = "1.0.13" +futures-util = { version = "0.3.7", default-features = false, features = ["std"] } +rand = "0.8" +rcgen = "0.8" +rustls-pemfile = "0.2" +static_assertions = "1" +tls-openssl = { package = "openssl", version = "0.10.9" } +tls-rustls = { package = "rustls", version = "0.20.0" } +zstd = "0.9" + +[[test]] +name = "test_server" +required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] + +[[test]] +name = "compression" +required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] + +[[example]] +name = "basic" +required-features = ["compress-gzip"] + +[[example]] +name = "uds" +required-features = ["compress-gzip"] + +[[example]] +name = "on-connect" +required-features = [] + +[[bench]] +name = "server" +harness = false + +[[bench]] +name = "service" +harness = false + +[[bench]] +name = "responder" +harness = false diff --git a/actix-web/LICENSE-APACHE b/actix-web/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-web/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-web/LICENSE-MIT b/actix-web/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-web/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/MIGRATION.md b/actix-web/MIGRATION.md similarity index 100% rename from MIGRATION.md rename to actix-web/MIGRATION.md diff --git a/actix-web/README.md b/actix-web/README.md new file mode 100644 index 000000000..f99a7be23 --- /dev/null +++ b/actix-web/README.md @@ -0,0 +1,105 @@ +
+

Actix Web

+

+ Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust +

+

+ +[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.1)](https://docs.rs/actix-web/4.0.0-rc.1) +![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.1) +
+[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) +![downloads](https://img.shields.io/crates/d/actix-web.svg) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + +

+
+ +## Features + +- Supports _HTTP/1.x_ and _HTTP/2_ +- Streaming and pipelining +- Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros +- Full [Tokio](https://tokio.rs) compatibility +- Keep-alive and slow requests handling +- Client/server [WebSockets](https://actix.rs/docs/websockets/) support +- Transparent content compression/decompression (br, gzip, deflate, zstd) +- Multipart streams +- Static assets +- SSL support using OpenSSL or Rustls +- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) +- Includes an async [HTTP client](https://docs.rs/awc/) +- Runs on stable Rust 1.54+ + +## Documentation + +- [Website & User Guide](https://actix.rs) +- [Examples Repository](https://github.com/actix/examples) +- [API Documentation](https://docs.rs/actix-web) +- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web) + +## Example + +Dependencies: + +```toml +[dependencies] +actix-web = "4.0.0-rc.1" +``` + +Code: + +```rust +use actix_web::{get, web, App, HttpServer, Responder}; + +#[get("/{id}/{name}/index.html")] +async fn index(params: web::Path<(u32, String)>) -> impl Responder { + let (id, name) = params.into_inner(); + format!("Hello {}! id:{}", name, id) +} + +#[actix_web::main] // or #[tokio::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| App::new().service(index)) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` + +### More examples + +- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) +- [Application State](https://github.com/actix/examples/tree/master/basics/state/) +- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) +- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) +- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) +- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) +- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) +- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) +- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) +- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) +- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) +- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) + +You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. + +## Benchmarks + +One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). + +## License + +This project is licensed under either of the following licenses, at your option: + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0]) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT]) + +## Code of Conduct + +Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. +The Actix team promises to intervene to uphold that code of conduct. diff --git a/benches/responder.rs b/actix-web/benches/responder.rs similarity index 100% rename from benches/responder.rs rename to actix-web/benches/responder.rs diff --git a/benches/server.rs b/actix-web/benches/server.rs similarity index 100% rename from benches/server.rs rename to actix-web/benches/server.rs diff --git a/benches/service.rs b/actix-web/benches/service.rs similarity index 100% rename from benches/service.rs rename to actix-web/benches/service.rs diff --git a/examples/README.md b/actix-web/examples/README.md similarity index 100% rename from examples/README.md rename to actix-web/examples/README.md diff --git a/examples/basic.rs b/actix-web/examples/basic.rs similarity index 100% rename from examples/basic.rs rename to actix-web/examples/basic.rs diff --git a/examples/on-connect.rs b/actix-web/examples/on-connect.rs similarity index 100% rename from examples/on-connect.rs rename to actix-web/examples/on-connect.rs diff --git a/examples/uds.rs b/actix-web/examples/uds.rs similarity index 100% rename from examples/uds.rs rename to actix-web/examples/uds.rs diff --git a/src/app.rs b/actix-web/src/app.rs similarity index 100% rename from src/app.rs rename to actix-web/src/app.rs diff --git a/src/app_service.rs b/actix-web/src/app_service.rs similarity index 100% rename from src/app_service.rs rename to actix-web/src/app_service.rs diff --git a/src/config.rs b/actix-web/src/config.rs similarity index 100% rename from src/config.rs rename to actix-web/src/config.rs diff --git a/src/data.rs b/actix-web/src/data.rs similarity index 100% rename from src/data.rs rename to actix-web/src/data.rs diff --git a/src/dev.rs b/actix-web/src/dev.rs similarity index 100% rename from src/dev.rs rename to actix-web/src/dev.rs diff --git a/src/error/error.rs b/actix-web/src/error/error.rs similarity index 100% rename from src/error/error.rs rename to actix-web/src/error/error.rs diff --git a/src/error/internal.rs b/actix-web/src/error/internal.rs similarity index 100% rename from src/error/internal.rs rename to actix-web/src/error/internal.rs diff --git a/src/error/macros.rs b/actix-web/src/error/macros.rs similarity index 100% rename from src/error/macros.rs rename to actix-web/src/error/macros.rs diff --git a/src/error/mod.rs b/actix-web/src/error/mod.rs similarity index 100% rename from src/error/mod.rs rename to actix-web/src/error/mod.rs diff --git a/src/error/response_error.rs b/actix-web/src/error/response_error.rs similarity index 100% rename from src/error/response_error.rs rename to actix-web/src/error/response_error.rs diff --git a/src/extract.rs b/actix-web/src/extract.rs similarity index 100% rename from src/extract.rs rename to actix-web/src/extract.rs diff --git a/src/guard.rs b/actix-web/src/guard.rs similarity index 100% rename from src/guard.rs rename to actix-web/src/guard.rs diff --git a/src/handler.rs b/actix-web/src/handler.rs similarity index 100% rename from src/handler.rs rename to actix-web/src/handler.rs diff --git a/src/helpers.rs b/actix-web/src/helpers.rs similarity index 100% rename from src/helpers.rs rename to actix-web/src/helpers.rs diff --git a/src/http/header/accept.rs b/actix-web/src/http/header/accept.rs similarity index 100% rename from src/http/header/accept.rs rename to actix-web/src/http/header/accept.rs diff --git a/src/http/header/accept_charset.rs b/actix-web/src/http/header/accept_charset.rs similarity index 100% rename from src/http/header/accept_charset.rs rename to actix-web/src/http/header/accept_charset.rs diff --git a/src/http/header/accept_encoding.rs b/actix-web/src/http/header/accept_encoding.rs similarity index 100% rename from src/http/header/accept_encoding.rs rename to actix-web/src/http/header/accept_encoding.rs diff --git a/src/http/header/accept_language.rs b/actix-web/src/http/header/accept_language.rs similarity index 100% rename from src/http/header/accept_language.rs rename to actix-web/src/http/header/accept_language.rs diff --git a/src/http/header/allow.rs b/actix-web/src/http/header/allow.rs similarity index 100% rename from src/http/header/allow.rs rename to actix-web/src/http/header/allow.rs diff --git a/src/http/header/any_or_some.rs b/actix-web/src/http/header/any_or_some.rs similarity index 100% rename from src/http/header/any_or_some.rs rename to actix-web/src/http/header/any_or_some.rs diff --git a/src/http/header/cache_control.rs b/actix-web/src/http/header/cache_control.rs similarity index 100% rename from src/http/header/cache_control.rs rename to actix-web/src/http/header/cache_control.rs diff --git a/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs similarity index 100% rename from src/http/header/content_disposition.rs rename to actix-web/src/http/header/content_disposition.rs diff --git a/src/http/header/content_language.rs b/actix-web/src/http/header/content_language.rs similarity index 100% rename from src/http/header/content_language.rs rename to actix-web/src/http/header/content_language.rs diff --git a/src/http/header/content_range.rs b/actix-web/src/http/header/content_range.rs similarity index 100% rename from src/http/header/content_range.rs rename to actix-web/src/http/header/content_range.rs diff --git a/src/http/header/content_type.rs b/actix-web/src/http/header/content_type.rs similarity index 100% rename from src/http/header/content_type.rs rename to actix-web/src/http/header/content_type.rs diff --git a/src/http/header/date.rs b/actix-web/src/http/header/date.rs similarity index 100% rename from src/http/header/date.rs rename to actix-web/src/http/header/date.rs diff --git a/src/http/header/encoding.rs b/actix-web/src/http/header/encoding.rs similarity index 100% rename from src/http/header/encoding.rs rename to actix-web/src/http/header/encoding.rs diff --git a/src/http/header/entity.rs b/actix-web/src/http/header/entity.rs similarity index 100% rename from src/http/header/entity.rs rename to actix-web/src/http/header/entity.rs diff --git a/src/http/header/etag.rs b/actix-web/src/http/header/etag.rs similarity index 100% rename from src/http/header/etag.rs rename to actix-web/src/http/header/etag.rs diff --git a/src/http/header/expires.rs b/actix-web/src/http/header/expires.rs similarity index 100% rename from src/http/header/expires.rs rename to actix-web/src/http/header/expires.rs diff --git a/src/http/header/if_match.rs b/actix-web/src/http/header/if_match.rs similarity index 100% rename from src/http/header/if_match.rs rename to actix-web/src/http/header/if_match.rs diff --git a/src/http/header/if_modified_since.rs b/actix-web/src/http/header/if_modified_since.rs similarity index 100% rename from src/http/header/if_modified_since.rs rename to actix-web/src/http/header/if_modified_since.rs diff --git a/src/http/header/if_none_match.rs b/actix-web/src/http/header/if_none_match.rs similarity index 100% rename from src/http/header/if_none_match.rs rename to actix-web/src/http/header/if_none_match.rs diff --git a/src/http/header/if_range.rs b/actix-web/src/http/header/if_range.rs similarity index 100% rename from src/http/header/if_range.rs rename to actix-web/src/http/header/if_range.rs diff --git a/src/http/header/if_unmodified_since.rs b/actix-web/src/http/header/if_unmodified_since.rs similarity index 100% rename from src/http/header/if_unmodified_since.rs rename to actix-web/src/http/header/if_unmodified_since.rs diff --git a/src/http/header/last_modified.rs b/actix-web/src/http/header/last_modified.rs similarity index 100% rename from src/http/header/last_modified.rs rename to actix-web/src/http/header/last_modified.rs diff --git a/src/http/header/macros.rs b/actix-web/src/http/header/macros.rs similarity index 100% rename from src/http/header/macros.rs rename to actix-web/src/http/header/macros.rs diff --git a/src/http/header/mod.rs b/actix-web/src/http/header/mod.rs similarity index 100% rename from src/http/header/mod.rs rename to actix-web/src/http/header/mod.rs diff --git a/src/http/header/preference.rs b/actix-web/src/http/header/preference.rs similarity index 100% rename from src/http/header/preference.rs rename to actix-web/src/http/header/preference.rs diff --git a/src/http/header/range.rs b/actix-web/src/http/header/range.rs similarity index 100% rename from src/http/header/range.rs rename to actix-web/src/http/header/range.rs diff --git a/src/http/mod.rs b/actix-web/src/http/mod.rs similarity index 100% rename from src/http/mod.rs rename to actix-web/src/http/mod.rs diff --git a/src/info.rs b/actix-web/src/info.rs similarity index 100% rename from src/info.rs rename to actix-web/src/info.rs diff --git a/src/lib.rs b/actix-web/src/lib.rs similarity index 100% rename from src/lib.rs rename to actix-web/src/lib.rs diff --git a/src/middleware/compat.rs b/actix-web/src/middleware/compat.rs similarity index 100% rename from src/middleware/compat.rs rename to actix-web/src/middleware/compat.rs diff --git a/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs similarity index 100% rename from src/middleware/compress.rs rename to actix-web/src/middleware/compress.rs diff --git a/src/middleware/condition.rs b/actix-web/src/middleware/condition.rs similarity index 100% rename from src/middleware/condition.rs rename to actix-web/src/middleware/condition.rs diff --git a/src/middleware/default_headers.rs b/actix-web/src/middleware/default_headers.rs similarity index 100% rename from src/middleware/default_headers.rs rename to actix-web/src/middleware/default_headers.rs diff --git a/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs similarity index 100% rename from src/middleware/err_handlers.rs rename to actix-web/src/middleware/err_handlers.rs diff --git a/src/middleware/logger.rs b/actix-web/src/middleware/logger.rs similarity index 100% rename from src/middleware/logger.rs rename to actix-web/src/middleware/logger.rs diff --git a/src/middleware/mod.rs b/actix-web/src/middleware/mod.rs similarity index 100% rename from src/middleware/mod.rs rename to actix-web/src/middleware/mod.rs diff --git a/src/middleware/noop.rs b/actix-web/src/middleware/noop.rs similarity index 100% rename from src/middleware/noop.rs rename to actix-web/src/middleware/noop.rs diff --git a/src/middleware/normalize.rs b/actix-web/src/middleware/normalize.rs similarity index 100% rename from src/middleware/normalize.rs rename to actix-web/src/middleware/normalize.rs diff --git a/src/request.rs b/actix-web/src/request.rs similarity index 100% rename from src/request.rs rename to actix-web/src/request.rs diff --git a/src/request_data.rs b/actix-web/src/request_data.rs similarity index 100% rename from src/request_data.rs rename to actix-web/src/request_data.rs diff --git a/src/resource.rs b/actix-web/src/resource.rs similarity index 100% rename from src/resource.rs rename to actix-web/src/resource.rs diff --git a/src/response/builder.rs b/actix-web/src/response/builder.rs similarity index 100% rename from src/response/builder.rs rename to actix-web/src/response/builder.rs diff --git a/src/response/customize_responder.rs b/actix-web/src/response/customize_responder.rs similarity index 100% rename from src/response/customize_responder.rs rename to actix-web/src/response/customize_responder.rs diff --git a/src/response/http_codes.rs b/actix-web/src/response/http_codes.rs similarity index 100% rename from src/response/http_codes.rs rename to actix-web/src/response/http_codes.rs diff --git a/src/response/mod.rs b/actix-web/src/response/mod.rs similarity index 100% rename from src/response/mod.rs rename to actix-web/src/response/mod.rs diff --git a/src/response/responder.rs b/actix-web/src/response/responder.rs similarity index 100% rename from src/response/responder.rs rename to actix-web/src/response/responder.rs diff --git a/src/response/response.rs b/actix-web/src/response/response.rs similarity index 100% rename from src/response/response.rs rename to actix-web/src/response/response.rs diff --git a/src/rmap.rs b/actix-web/src/rmap.rs similarity index 100% rename from src/rmap.rs rename to actix-web/src/rmap.rs diff --git a/src/route.rs b/actix-web/src/route.rs similarity index 100% rename from src/route.rs rename to actix-web/src/route.rs diff --git a/src/scope.rs b/actix-web/src/scope.rs similarity index 100% rename from src/scope.rs rename to actix-web/src/scope.rs diff --git a/src/server.rs b/actix-web/src/server.rs similarity index 100% rename from src/server.rs rename to actix-web/src/server.rs diff --git a/src/service.rs b/actix-web/src/service.rs similarity index 100% rename from src/service.rs rename to actix-web/src/service.rs diff --git a/src/test/mod.rs b/actix-web/src/test/mod.rs similarity index 100% rename from src/test/mod.rs rename to actix-web/src/test/mod.rs diff --git a/src/test/test_request.rs b/actix-web/src/test/test_request.rs similarity index 100% rename from src/test/test_request.rs rename to actix-web/src/test/test_request.rs diff --git a/src/test/test_services.rs b/actix-web/src/test/test_services.rs similarity index 100% rename from src/test/test_services.rs rename to actix-web/src/test/test_services.rs diff --git a/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs similarity index 100% rename from src/test/test_utils.rs rename to actix-web/src/test/test_utils.rs diff --git a/src/types/either.rs b/actix-web/src/types/either.rs similarity index 100% rename from src/types/either.rs rename to actix-web/src/types/either.rs diff --git a/src/types/form.rs b/actix-web/src/types/form.rs similarity index 100% rename from src/types/form.rs rename to actix-web/src/types/form.rs diff --git a/src/types/header.rs b/actix-web/src/types/header.rs similarity index 100% rename from src/types/header.rs rename to actix-web/src/types/header.rs diff --git a/src/types/json.rs b/actix-web/src/types/json.rs similarity index 100% rename from src/types/json.rs rename to actix-web/src/types/json.rs diff --git a/src/types/mod.rs b/actix-web/src/types/mod.rs similarity index 100% rename from src/types/mod.rs rename to actix-web/src/types/mod.rs diff --git a/src/types/path.rs b/actix-web/src/types/path.rs similarity index 100% rename from src/types/path.rs rename to actix-web/src/types/path.rs diff --git a/src/types/payload.rs b/actix-web/src/types/payload.rs similarity index 100% rename from src/types/payload.rs rename to actix-web/src/types/payload.rs diff --git a/src/types/query.rs b/actix-web/src/types/query.rs similarity index 100% rename from src/types/query.rs rename to actix-web/src/types/query.rs diff --git a/src/types/readlines.rs b/actix-web/src/types/readlines.rs similarity index 100% rename from src/types/readlines.rs rename to actix-web/src/types/readlines.rs diff --git a/src/web.rs b/actix-web/src/web.rs similarity index 100% rename from src/web.rs rename to actix-web/src/web.rs diff --git a/tests/compression.rs b/actix-web/tests/compression.rs similarity index 100% rename from tests/compression.rs rename to actix-web/tests/compression.rs diff --git a/tests/fixtures/lorem.txt b/actix-web/tests/fixtures/lorem.txt similarity index 100% rename from tests/fixtures/lorem.txt rename to actix-web/tests/fixtures/lorem.txt diff --git a/tests/fixtures/lorem.txt.br b/actix-web/tests/fixtures/lorem.txt.br similarity index 100% rename from tests/fixtures/lorem.txt.br rename to actix-web/tests/fixtures/lorem.txt.br diff --git a/tests/fixtures/lorem.txt.gz b/actix-web/tests/fixtures/lorem.txt.gz similarity index 100% rename from tests/fixtures/lorem.txt.gz rename to actix-web/tests/fixtures/lorem.txt.gz diff --git a/tests/fixtures/lorem.txt.xz b/actix-web/tests/fixtures/lorem.txt.xz similarity index 100% rename from tests/fixtures/lorem.txt.xz rename to actix-web/tests/fixtures/lorem.txt.xz diff --git a/tests/fixtures/lorem.txt.zst b/actix-web/tests/fixtures/lorem.txt.zst similarity index 100% rename from tests/fixtures/lorem.txt.zst rename to actix-web/tests/fixtures/lorem.txt.zst diff --git a/tests/test-macro-import-conflict.rs b/actix-web/tests/test-macro-import-conflict.rs similarity index 100% rename from tests/test-macro-import-conflict.rs rename to actix-web/tests/test-macro-import-conflict.rs diff --git a/tests/test_error_propagation.rs b/actix-web/tests/test_error_propagation.rs similarity index 100% rename from tests/test_error_propagation.rs rename to actix-web/tests/test_error_propagation.rs diff --git a/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs similarity index 100% rename from tests/test_httpserver.rs rename to actix-web/tests/test_httpserver.rs diff --git a/tests/test_server.rs b/actix-web/tests/test_server.rs similarity index 100% rename from tests/test_server.rs rename to actix-web/tests/test_server.rs diff --git a/tests/test_weird_poll.rs b/actix-web/tests/test_weird_poll.rs similarity index 100% rename from tests/test_weird_poll.rs rename to actix-web/tests/test_weird_poll.rs diff --git a/tests/utils.rs b/actix-web/tests/utils.rs similarity index 100% rename from tests/utils.rs rename to actix-web/tests/utils.rs diff --git a/tests/weird_poll.rs b/actix-web/tests/weird_poll.rs similarity index 100% rename from tests/weird_poll.rs rename to actix-web/tests/weird_poll.rs From 7f5a8c08514e05a01995aa33c8ff49eb06056ea0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 00:33:41 +0000 Subject: [PATCH 337/861] fix vmanifest --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7eed68e54..26b5b91b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ actix-http-test = { path = "actix-http-test" } actix-multipart = { path = "actix-multipart" } actix-router = { path = "actix-router" } actix-test = { path = "actix-test" } -actix-web = { path = "." } +actix-web = { path = "actix-web" } actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } awc = { path = "awc" } From 40a4b1ccd54e0dd2d5bd4b8c10601b2e26335ec0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 02:35:05 +0000 Subject: [PATCH 338/861] add macro feature (#2619) Co-authored-by: Ibraheem Ahmed --- .cargo/config.toml | 5 +++- actix-web/CHANGES.md | 7 ++++++ actix-web/Cargo.toml | 36 +++++++++++++++++----------- actix-web/examples/macroless.rs | 21 +++++++++++++++++ actix-web/src/app.rs | 3 +-- actix-web/src/lib.rs | 42 ++++++++++++++++++++++++++------- actix-web/src/rt.rs | 38 +++++++++++++++++++++++++++++ actix-web/src/service.rs | 7 +++--- 8 files changed, 130 insertions(+), 29 deletions(-) create mode 100644 actix-web/examples/macroless.rs create mode 100644 actix-web/src/rt.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 4425e0dda..deb300749 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,9 +6,12 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli ci-check-min = "hack --workspace check --no-default-features" ci-check-default = "hack --workspace check" ci-check-default-tests = "check --workspace --tests" -ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check" +ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check" ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check" # testing ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" + +# compile docs as docs.rs would +# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index a7e0a0309..77918341a 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- On-by-default `macros` feature flag to enable routing and runtime macros. [#2619] + +### Removed +- `rt::{Arbiter, ArbiterHandle}` re-exports. [#2619] + +[#2601]: https://github.com/actix/actix-web/pull/2601 ## 4.0.0-rc.1 - 2022-01-31 diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 4e10dce18..92c303b5c 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -20,7 +20,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] +features = ["macros", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] rustdoc-args = ["--cfg", "docsrs"] [lib] @@ -28,7 +28,7 @@ name = "actix_web" path = "src/lib.rs" [features] -default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] +default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"] # Brotli algorithm content-encoding support compress-brotli = ["actix-http/compress-brotli", "__compress"] @@ -37,16 +37,23 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"] # Zstd algorithm content-encoding support compress-zstd = ["actix-http/compress-zstd", "__compress"] -# support for cookies +# Routing and runtime proc macros +macros = [ + "actix-rt/macros", + "actix-macros", + "actix-web-codegen", +] + +# Cookies support cookies = ["cookie"] -# secure cookies feature -secure-cookies = ["cookie/secure"] +# Secure & signed cookies +secure-cookies = ["cookies", "cookie/secure"] -# openssl +# TLS via OpenSSL openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] -# rustls +# TLS via Rustls rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] # Internal (PRIVATE!) features used to aid testing and checking feature status. @@ -58,16 +65,16 @@ experimental-io-uring = ["actix-server/io-uring"] [dependencies] actix-codec = "0.4.1" -actix-macros = "0.2.3" -actix-rt = "2.6" +actix-macros = { version = "0.2.3", optional = true } +actix-rt = { version = "2.6", default-features = false } actix-server = "2" -actix-service = "2.0.0" -actix-utils = "3.0.0" -actix-tls = { version = "3.0.0", default-features = false, optional = true } +actix-service = "2" +actix-utils = "3" +actix-tls = { version = "3", default-features = false, optional = true } actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" -actix-web-codegen = "0.5.0-rc.2" +actix-web-codegen = { version = "0.5.0-rc.2", optional = true } ahash = "0.7" bytes = "1" @@ -84,7 +91,7 @@ log = "0.4" mime = "0.3" pin-project-lite = "0.2.7" regex = "1.4" -serde = { version = "1.0", features = ["derive"] } +serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6.1" @@ -106,6 +113,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"] rand = "0.8" rcgen = "0.8" rustls-pemfile = "0.2" +serde = { version = "1.0", features = ["derive"] } static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } diff --git a/actix-web/examples/macroless.rs b/actix-web/examples/macroless.rs new file mode 100644 index 000000000..78ffd45c1 --- /dev/null +++ b/actix-web/examples/macroless.rs @@ -0,0 +1,21 @@ +use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; + +async fn index(req: HttpRequest) -> &'static str { + println!("REQ: {:?}", req); + "Hello world!\r\n" +} + +fn main() -> std::io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + rt::System::new().block_on( + HttpServer::new(|| { + App::new() + .wrap(middleware::Logger::default()) + .service(web::resource("/").route(web::get().to(index))) + }) + .bind(("127.0.0.1", 8080))? + .workers(1) + .run(), + ) +} diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index a63cf5d50..be3526393 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -21,8 +21,7 @@ use crate::{ }, }; -/// Application builder - structure that follows the builder pattern -/// for building application instances. +/// The top-level builder for an Actix Web application. pub struct App { endpoint: T, services: Vec>, diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 18f0d581d..c3313db81 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -57,6 +57,7 @@ //! //! # Crate Features //! * `cookies` - cookies support (enabled by default) +//! * `macros` - routing and runtime macros (enabled by default) //! * `compress-brotli` - brotli content encoding compression support (enabled by default) //! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default) //! * `compress-zstd` - zstd content encoding compression support (enabled by default) @@ -68,6 +69,7 @@ #![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_cfg))] mod app; mod app_service; @@ -88,6 +90,7 @@ mod resource; mod response; mod rmap; mod route; +pub mod rt; mod scope; mod server; mod service; @@ -95,15 +98,10 @@ pub mod test; pub(crate) mod types; pub mod web; -pub use actix_http::{body, HttpMessage}; -#[doc(inline)] -pub use actix_rt as rt; -pub use actix_web_codegen::*; -#[cfg(feature = "cookies")] -pub use cookie; - pub use crate::app::App; -pub use crate::error::{Error, ResponseError, Result}; +#[doc(inline)] +pub use crate::error::Result; +pub use crate::error::{Error, ResponseError}; pub use crate::extract::FromRequest; pub use crate::handler::Handler; pub use crate::request::HttpRequest; @@ -114,4 +112,32 @@ pub use crate::scope::Scope; pub use crate::server::HttpServer; pub use crate::types::Either; +pub use actix_http::{body, HttpMessage}; + +#[cfg(feature = "cookies")] +#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] +#[doc(inline)] +pub use cookie; + +macro_rules! codegen_reexport { + ($name:ident) => { + #[cfg(feature = "macros")] + #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] + pub use actix_web_codegen::$name; + }; +} + +codegen_reexport!(main); +codegen_reexport!(test); +codegen_reexport!(route); +codegen_reexport!(head); +codegen_reexport!(get); +codegen_reexport!(post); +codegen_reexport!(patch); +codegen_reexport!(put); +codegen_reexport!(delete); +codegen_reexport!(trace); +codegen_reexport!(connect); +codegen_reexport!(options); + pub(crate) type BoxError = Box; diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs new file mode 100644 index 000000000..87d76048f --- /dev/null +++ b/actix-web/src/rt.rs @@ -0,0 +1,38 @@ +//! A selection of re-exports from [`actix-rt`] and [`tokio`]. +//! +//! [`actix-rt`]: https://docs.rs/actix_rt +//! [`tokio`]: https://docs.rs/tokio +//! +//! # Running Actix Web Macro-less +//! ```no_run +//! use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; +//! +//! async fn index(req: HttpRequest) -> &'static str { +//! println!("REQ: {:?}", req); +//! "Hello world!\r\n" +//! } +//! +//! # fn main() -> std::io::Result<()> { +//! rt::System::new().block_on( +//! HttpServer::new(|| { +//! App::new() +//! .wrap(middleware::Logger::default()) +//! .service(web::resource("/").route(web::get().to(index))) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .workers(1) +//! .run() +//! ) +//! # } +//! ``` + +// In particular: +// - Omit the `Arbiter` types because they have limited value here. +// - Re-export but hide the runtime macros because they won't work directly but are required for +// `#[actix_web::main]` and `#[actix_web::test]` to work. + +pub use actix_rt::{net, pin, signal, spawn, task, time, Runtime, System, SystemRunner}; + +#[cfg(feature = "macros")] +#[doc(hidden)] +pub use actix_rt::{main, test}; diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 061c3e044..3843abcf8 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -610,11 +610,10 @@ where } } -/// Macro helping register different types of services at the sametime. +/// Macro to help register different types of services at the same time. /// -/// The service type must be implementing [`HttpServiceFactory`](self::HttpServiceFactory) trait. -/// -/// The max number of services can be grouped together is 12. +/// The max number of services that can be grouped together is 12 and all must implement the +/// [`HttpServiceFactory`] trait. /// /// # Examples /// ``` From a68239adaa63b8fc0940af1ec30f4005e13677b4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 13:35:32 +0000 Subject: [PATCH 339/861] bump zstd to 0.10 --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- scripts/ci-test | 20 ++++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b592c98da..d8458b2fc 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,7 +94,7 @@ actix-tls = { version = "3.0.0", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.9", optional = true } +zstd = { version = "0.10", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 92c303b5c..b4908d25b 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -117,7 +117,7 @@ serde = { version = "1.0", features = ["derive"] } static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } -zstd = "0.9" +zstd = "0.10" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f2bf7526d..e85eeb37a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -110,7 +110,7 @@ static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.9" +zstd = "0.10" [[example]] name = "client" diff --git a/scripts/ci-test b/scripts/ci-test index 8b7e3d12d..bdea1283a 100755 --- a/scripts/ci-test +++ b/scripts/ci-test @@ -13,17 +13,17 @@ save_exit_code() { } save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture --skip=test_reading_deflate_encoding_large_random_rustls -# save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture -# save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture +save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture -# save_exit_code cargo test --workspace --doc +save_exit_code cargo test --workspace --doc if [ "$EXIT" = "0" ]; then PASSED="All tests passed!" From e9279dfbb857bd3e2ff41704f9562886d71dff20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hromada?= Date: Tue, 1 Feb 2022 14:44:56 +0100 Subject: [PATCH 340/861] Fix deprecated notice about client_shutdown (#2621) --- actix-web/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 83e025fb0..c9d9cc9bd 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -226,7 +226,7 @@ where } #[doc(hidden)] - #[deprecated(since = "4.0.0", note = "Renamed to `client_request_timeout`.")] + #[deprecated(since = "4.0.0", note = "Renamed to `client_disconnect_timeout`.")] pub fn client_shutdown(self, dur: u64) -> Self { self.client_disconnect_timeout(Duration::from_millis(dur)) } From c84c1f0f15ca3b819ac642a382be1992e11f711e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 14:15:30 +0000 Subject: [PATCH 341/861] simplify macros feature --- actix-http/tests/test_openssl.rs | 28 +++++++++---------- actix-http/tests/test_rustls.rs | 38 ++++++++++++------------- actix-web/Cargo.toml | 1 - actix-web/src/rt.rs | 2 +- awc/tests/test_client.rs | 48 ++++++++++++++++---------------- 5 files changed, 58 insertions(+), 59 deletions(-) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 1e371473f..35321ac98 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -66,7 +66,7 @@ fn tls_config() -> SslAcceptor { } #[actix_rt::test] -async fn test_h2() -> io::Result<()> { +async fn h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Error>(Response::ok())) @@ -81,7 +81,7 @@ async fn test_h2() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_1() -> io::Result<()> { +async fn h2_1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .finish(|req: Request| { @@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_body() -> io::Result<()> { +async fn h2_body() -> io::Result<()> { let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB let mut srv = test_server(move || { HttpService::build() @@ -122,7 +122,7 @@ async fn test_h2_body() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_content_length() { +async fn h2_content_length() { let srv = test_server(move || { HttpService::build() .h2(|req: Request| { @@ -164,7 +164,7 @@ async fn test_h2_content_length() { } #[actix_rt::test] -async fn test_h2_headers() { +async fn h2_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -229,7 +229,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h2_body2() { +async fn h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -247,7 +247,7 @@ async fn test_h2_body2() { } #[actix_rt::test] -async fn test_h2_head_empty() { +async fn h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -271,7 +271,7 @@ async fn test_h2_head_empty() { } #[actix_rt::test] -async fn test_h2_head_binary() { +async fn h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -294,7 +294,7 @@ async fn test_h2_head_binary() { } #[actix_rt::test] -async fn test_h2_head_binary2() { +async fn h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -313,7 +313,7 @@ async fn test_h2_head_binary2() { } #[actix_rt::test] -async fn test_h2_body_length() { +async fn h2_body_length() { let mut srv = test_server(move || { HttpService::build() .h2(|_| async { @@ -338,7 +338,7 @@ async fn test_h2_body_length() { } #[actix_rt::test] -async fn test_h2_body_chunked_explicit() { +async fn h2_body_chunked_explicit() { let mut srv = test_server(move || { HttpService::build() .h2(|_| { @@ -366,7 +366,7 @@ async fn test_h2_body_chunked_explicit() { } #[actix_rt::test] -async fn test_h2_response_http_error_handling() { +async fn h2_response_http_error_handling() { let mut srv = test_server(move || { HttpService::build() .h2(fn_service(|_| { @@ -406,7 +406,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h2_service_error() { +async fn h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::, _>(BadRequest)) @@ -424,7 +424,7 @@ async fn test_h2_service_error() { } #[actix_rt::test] -async fn test_h2_on_connect() { +async fn h2_on_connect() { let srv = test_server(move || { HttpService::build() .on_connect_ext(|_, data| { diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 51fefae72..8e59ec65d 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -106,7 +106,7 @@ pub fn get_negotiated_alpn_protocol( } #[actix_rt::test] -async fn test_h1() -> io::Result<()> { +async fn h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h1(|_| ok::<_, Error>(Response::ok())) @@ -120,7 +120,7 @@ async fn test_h1() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2() -> io::Result<()> { +async fn h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Error>(Response::ok())) @@ -134,7 +134,7 @@ async fn test_h2() -> io::Result<()> { } #[actix_rt::test] -async fn test_h1_1() -> io::Result<()> { +async fn h1_1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h1(|req: Request| { @@ -152,7 +152,7 @@ async fn test_h1_1() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_1() -> io::Result<()> { +async fn h2_1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .finish(|req: Request| { @@ -170,7 +170,7 @@ async fn test_h2_1() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_body1() -> io::Result<()> { +async fn h2_body1() -> io::Result<()> { let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let mut srv = test_server(move || { HttpService::build() @@ -191,7 +191,7 @@ async fn test_h2_body1() -> io::Result<()> { } #[actix_rt::test] -async fn test_h2_content_length() { +async fn h2_content_length() { let srv = test_server(move || { HttpService::build() .h2(|req: Request| { @@ -245,7 +245,7 @@ async fn test_h2_content_length() { } #[actix_rt::test] -async fn test_h2_headers() { +async fn h2_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -309,7 +309,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[actix_rt::test] -async fn test_h2_body2() { +async fn h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -326,7 +326,7 @@ async fn test_h2_body2() { } #[actix_rt::test] -async fn test_h2_head_empty() { +async fn h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -352,7 +352,7 @@ async fn test_h2_head_empty() { } #[actix_rt::test] -async fn test_h2_head_binary() { +async fn h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -377,7 +377,7 @@ async fn test_h2_head_binary() { } #[actix_rt::test] -async fn test_h2_head_binary2() { +async fn h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) @@ -398,7 +398,7 @@ async fn test_h2_head_binary2() { } #[actix_rt::test] -async fn test_h2_body_length() { +async fn h2_body_length() { let mut srv = test_server(move || { HttpService::build() .h2(|_| { @@ -420,7 +420,7 @@ async fn test_h2_body_length() { } #[actix_rt::test] -async fn test_h2_body_chunked_explicit() { +async fn h2_body_chunked_explicit() { let mut srv = test_server(move || { HttpService::build() .h2(|_| { @@ -447,7 +447,7 @@ async fn test_h2_body_chunked_explicit() { } #[actix_rt::test] -async fn test_h2_response_http_error_handling() { +async fn h2_response_http_error_handling() { let mut srv = test_server(move || { HttpService::build() .h2(fn_factory_with_config(|_: ()| { @@ -486,7 +486,7 @@ impl From for Response { } #[actix_rt::test] -async fn test_h2_service_error() { +async fn h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::, _>(BadRequest)) @@ -503,7 +503,7 @@ async fn test_h2_service_error() { } #[actix_rt::test] -async fn test_h1_service_error() { +async fn h1_service_error() { let mut srv = test_server(move || { HttpService::build() .h1(|_| err::, _>(BadRequest)) @@ -524,7 +524,7 @@ const HTTP1_1_ALPN_PROTOCOL: &[u8] = b"http/1.1"; const CUSTOM_ALPN_PROTOCOL: &[u8] = b"custom"; #[actix_rt::test] -async fn test_alpn_h1() -> io::Result<()> { +async fn alpn_h1() -> io::Result<()> { let srv = test_server(move || { let mut config = tls_config(); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); @@ -546,7 +546,7 @@ async fn test_alpn_h1() -> io::Result<()> { } #[actix_rt::test] -async fn test_alpn_h2() -> io::Result<()> { +async fn alpn_h2() -> io::Result<()> { let srv = test_server(move || { let mut config = tls_config(); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); @@ -572,7 +572,7 @@ async fn test_alpn_h2() -> io::Result<()> { } #[actix_rt::test] -async fn test_alpn_h2_1() -> io::Result<()> { +async fn alpn_h2_1() -> io::Result<()> { let srv = test_server(move || { let mut config = tls_config(); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index b4908d25b..bdfdcb191 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -39,7 +39,6 @@ compress-zstd = ["actix-http/compress-zstd", "__compress"] # Routing and runtime proc macros macros = [ - "actix-rt/macros", "actix-macros", "actix-web-codegen", ] diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs index 87d76048f..efe9fdfe6 100644 --- a/actix-web/src/rt.rs +++ b/actix-web/src/rt.rs @@ -35,4 +35,4 @@ pub use actix_rt::{net, pin, signal, spawn, task, time, Runtime, System, SystemR #[cfg(feature = "macros")] #[doc(hidden)] -pub use actix_rt::{main, test}; +pub use actix_macros::{main, test}; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index dceaf467d..165d8faf0 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -28,7 +28,7 @@ const S: &str = "Hello World "; const STR: &str = const_str::repeat!(S, 100); #[actix_rt::test] -async fn test_simple() { +async fn simple() { let srv = actix_test::start(|| { App::new().service( web::resource("/").route(web::to(|| async { HttpResponse::Ok().body(STR) })), @@ -56,7 +56,7 @@ async fn test_simple() { } #[actix_rt::test] -async fn test_json() { +async fn json() { let srv = actix_test::start(|| { App::new().service( web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), @@ -72,7 +72,7 @@ async fn test_json() { } #[actix_rt::test] -async fn test_form() { +async fn form() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to( |_: web::Form>| HttpResponse::Ok(), @@ -91,7 +91,7 @@ async fn test_form() { } #[actix_rt::test] -async fn test_timeout() { +async fn timeout() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; @@ -116,7 +116,7 @@ async fn test_timeout() { } #[actix_rt::test] -async fn test_timeout_override() { +async fn timeout_override() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; @@ -138,7 +138,7 @@ async fn test_timeout_override() { } #[actix_rt::test] -async fn test_response_timeout() { +async fn response_timeout() { use futures_util::stream::{once, StreamExt as _}; let srv = actix_test::start(|| { @@ -211,7 +211,7 @@ async fn test_response_timeout() { } #[actix_rt::test] -async fn test_connection_reuse() { +async fn connection_reuse() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -248,7 +248,7 @@ async fn test_connection_reuse() { } #[actix_rt::test] -async fn test_connection_force_close() { +async fn connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -285,7 +285,7 @@ async fn test_connection_force_close() { } #[actix_rt::test] -async fn test_connection_server_close() { +async fn connection_server_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -324,7 +324,7 @@ async fn test_connection_server_close() { } #[actix_rt::test] -async fn test_connection_wait_queue() { +async fn connection_wait_queue() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -373,7 +373,7 @@ async fn test_connection_wait_queue() { } #[actix_rt::test] -async fn test_connection_wait_queue_force_close() { +async fn connection_wait_queue_force_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -421,7 +421,7 @@ async fn test_connection_wait_queue_force_close() { } #[actix_rt::test] -async fn test_with_query_parameter() { +async fn with_query_parameter() { let srv = actix_test::start(|| { App::new().service(web::resource("/").to(|req: HttpRequest| { if req.query_string().contains("qp") { @@ -442,7 +442,7 @@ async fn test_with_query_parameter() { #[cfg(feature = "compress-gzip")] #[actix_rt::test] -async fn test_no_decompress() { +async fn no_decompress() { let srv = actix_test::start(|| { App::new() .wrap(actix_web::middleware::Compress::default()) @@ -480,7 +480,7 @@ async fn test_no_decompress() { #[cfg(feature = "compress-gzip")] #[actix_rt::test] -async fn test_client_gzip_encoding() { +async fn client_gzip_encoding() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { HttpResponse::Ok() @@ -500,7 +500,7 @@ async fn test_client_gzip_encoding() { #[cfg(feature = "compress-gzip")] #[actix_rt::test] -async fn test_client_gzip_encoding_large() { +async fn client_gzip_encoding_large() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { HttpResponse::Ok() @@ -520,7 +520,7 @@ async fn test_client_gzip_encoding_large() { #[cfg(feature = "compress-gzip")] #[actix_rt::test] -async fn test_client_gzip_encoding_large_random() { +async fn client_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(100_000) @@ -546,7 +546,7 @@ async fn test_client_gzip_encoding_large_random() { #[cfg(feature = "compress-brotli")] #[actix_rt::test] -async fn test_client_brotli_encoding() { +async fn client_brotli_encoding() { let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| async { HttpResponse::Ok() @@ -566,7 +566,7 @@ async fn test_client_brotli_encoding() { #[cfg(feature = "compress-brotli")] #[actix_rt::test] -async fn test_client_brotli_encoding_large_random() { +async fn client_brotli_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(70_000) @@ -591,7 +591,7 @@ async fn test_client_brotli_encoding_large_random() { } #[actix_rt::test] -async fn test_client_deflate_encoding() { +async fn client_deflate_encoding() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| async { HttpResponse::Ok().body(body) @@ -611,7 +611,7 @@ async fn test_client_deflate_encoding() { } #[actix_rt::test] -async fn test_client_deflate_encoding_large_random() { +async fn client_deflate_encoding_large_random() { let data = rand::thread_rng() .sample_iter(rand::distributions::Alphanumeric) .map(char::from) @@ -637,7 +637,7 @@ async fn test_client_deflate_encoding_large_random() { } #[actix_rt::test] -async fn test_client_streaming_explicit() { +async fn client_streaming_explicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| async { HttpResponse::Ok().streaming(body) @@ -659,7 +659,7 @@ async fn test_client_streaming_explicit() { } #[actix_rt::test] -async fn test_body_streaming_implicit() { +async fn body_streaming_implicit() { let srv = actix_test::start(|| { App::new().default_service(web::to(|| async { let body = @@ -681,7 +681,7 @@ async fn test_body_streaming_implicit() { } #[actix_rt::test] -async fn test_client_cookie_handling() { +async fn client_cookie_handling() { use std::io::{Error as IoError, ErrorKind}; let cookie1 = Cookie::build("cookie1", "value1").finish(); @@ -833,7 +833,7 @@ async fn client_bearer_auth() { } #[actix_rt::test] -async fn test_local_address() { +async fn local_address() { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let srv = actix_test::start(move || { From ccf430d74aae3dab11816c87d3439bdd27f85414 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Feb 2022 15:24:35 +0000 Subject: [PATCH 342/861] disable coverage job --- .../{ci-master.yml => ci-post-merge.yml} | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) rename .github/workflows/{ci-master.yml => ci-post-merge.yml} (82%) diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-post-merge.yml similarity index 82% rename from .github/workflows/ci-master.yml rename to .github/workflows/ci-post-merge.yml index b78617dc5..4ae925452 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-post-merge.yml @@ -1,4 +1,4 @@ -name: CI (master only) +name: CI (post-merge) on: push: @@ -125,29 +125,30 @@ jobs: uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset-linux } - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + # job currently (1st Feb 2022) segfaults + # coverage: + # name: coverage + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true + # - name: Install stable + # uses: actions-rs/toolchain@v1 + # with: + # toolchain: stable-x86_64-unknown-linux-gnu + # profile: minimal + # override: true - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 + # - name: Generate Cargo.lock + # uses: actions-rs/cargo@v1 + # with: { command: generate-lockfile } + # - name: Cache Dependencies + # uses: Swatinem/rust-cache@v1.2.0 - - name: Generate coverage file - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - - name: Upload to Codecov - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } + # - name: Generate coverage file + # run: | + # cargo install cargo-tarpaulin --vers "^0.13" + # cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + # - name: Upload to Codecov + # uses: codecov/codecov-action@v1 + # with: { file: cobertura.xml } From 0957ec40b4be77c8fbc379049fdfeb407dc1c9b6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 02:46:37 +0000 Subject: [PATCH 343/861] split migration file --- actix-web/MIGRATION-0.x.md | 198 +++++++++++ actix-web/MIGRATION-1.0.md | 337 ++++++++++++++++++ actix-web/MIGRATION-2.0.md | 48 +++ actix-web/MIGRATION-3.0.md | 53 +++ actix-web/MIGRATION-4.0.md | 37 ++ actix-web/MIGRATION.md | 677 ------------------------------------- 6 files changed, 673 insertions(+), 677 deletions(-) create mode 100644 actix-web/MIGRATION-0.x.md create mode 100644 actix-web/MIGRATION-1.0.md create mode 100644 actix-web/MIGRATION-2.0.md create mode 100644 actix-web/MIGRATION-3.0.md create mode 100644 actix-web/MIGRATION-4.0.md delete mode 100644 actix-web/MIGRATION.md diff --git a/actix-web/MIGRATION-0.x.md b/actix-web/MIGRATION-0.x.md new file mode 100644 index 000000000..1b60c36d1 --- /dev/null +++ b/actix-web/MIGRATION-0.x.md @@ -0,0 +1,198 @@ +# 0.7.15 + +- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in + your routes, you should use `%20`. + +instead of + +```rust +fn main() { + let app = App::new().resource("/my index", |r| { + r.method(http::Method::GET) + .with(index); + }); +} +``` + +use + +```rust +fn main() { + let app = App::new().resource("/my%20index", |r| { + r.method(http::Method::GET) + .with(index); + }); +} +``` + +- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` + +# 0.7.4 + +- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple + even for handler with one parameter. + +# 0.7 + +- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload + use `HttpMessage::payload()` method. + +instead of + +```rust +fn index(req: HttpRequest) -> impl Responder { + req + .from_err() + .fold(...) + .... +} +``` + +use `.payload()` + +```rust +fn index(req: HttpRequest) -> impl Responder { + req + .payload() // <- get request payload stream + .from_err() + .fold(...) + .... +} +``` + +- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) + trait uses `&HttpRequest` instead of `&mut HttpRequest`. + +- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. + +instead of + +```rust +fn index(query: Query<..>, info: Json impl Responder {} +``` + +use tuple of extractors and use `.with()` for registration: + +```rust +fn index((query, json): (Query<..>, Json impl Responder {} +``` + +- `Handler::handle()` uses `&self` instead of `&mut self` + +- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value + +- Removed deprecated `HttpServer::threads()`, use + [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. + +- Renamed `client::ClientConnectorError::Connector` to + `client::ClientConnectorError::Resolver` + +- `Route::with()` does not return `ExtractorConfig`, to configure + extractor use `Route::with_config()` + +instead of + +```rust +fn main() { + let app = App::new().resource("/index.html", |r| { + r.method(http::Method::GET) + .with(index) + .limit(4096); // <- limit size of the payload + }); +} +``` + +use + +```rust + +fn main() { + let app = App::new().resource("/index.html", |r| { + r.method(http::Method::GET) + .with_config(index, |cfg| { // <- register handler + cfg.limit(4096); // <- limit size of the payload + }) + }); +} +``` + +- `Route::with_async()` does not return `ExtractorConfig`, to configure + extractor use `Route::with_async_config()` + +# 0.6 + +- `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` + +- `ws::Message::Close` now includes optional close reason. + `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. + +- `HttpServer::threads()` renamed to `HttpServer::workers()`. + +- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. + Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. + +- `HttpRequest::extensions()` returns read only reference to the request's Extension + `HttpRequest::extensions_mut()` returns mutable reference. + +- Instead of + + `use actix_web::middleware::{ CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` + + use `actix_web::middleware::session` + + `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` + +- `FromRequest::from_request()` accepts mutable reference to a request + +- `FromRequest::Result` has to implement `Into>` + +- [`Responder::respond_to()`](https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) + is generic over `S` + +- Use `Query` extractor instead of HttpRequest::query()`. + +```rust +fn index(q: Query>) -> Result<..> { + ... +} +``` + +or + +```rust +let q = Query::>::extract(req); +``` + +- Websocket operations are implemented as `WsWriter` trait. + you need to use `use actix_web::ws::WsWriter` + +# 0.5 + +- `HttpResponseBuilder::body()`, `.finish()`, `.json()` + methods return `HttpResponse` instead of `Result` + +- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` + moved to `actix_web::http` module + +- `actix_web::header` moved to `actix_web::http::header` + +- `NormalizePath` moved to `actix_web::http` module + +- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, + shortcut for `actix_web::server::HttpServer::new()` + +- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself + +- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. + +- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type + +- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` + functions should be used instead + +- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` + instead of `Result<_, http::Error>` + +- `Application` renamed to a `App` + +- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/actix-web/MIGRATION-1.0.md b/actix-web/MIGRATION-1.0.md new file mode 100644 index 000000000..94c6321ac --- /dev/null +++ b/actix-web/MIGRATION-1.0.md @@ -0,0 +1,337 @@ +## 1.0.1 + +- Cors middleware has been moved to `actix-cors` crate + + instead of + + ```rust + use actix_web::middleware::cors::Cors; + ``` + + use + + ```rust + use actix_cors::Cors; + ``` + +- Identity middleware has been moved to `actix-identity` crate + + instead of + + ```rust + use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService}; + ``` + + use + + ```rust + use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; + ``` + +## 1.0.0 + +- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration + + instead of + + ```rust + + #[derive(Default)] + struct ExtractorConfig { + config: String, + } + + impl FromRequest for YourExtractor { + type Config = ExtractorConfig; + type Result = Result; + + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + println!("use the config: {:?}", cfg.config); + ... + } + } + + App::new().resource("/route_with_config", |r| { + r.post().with_config(handler_fn, |cfg| { + cfg.0.config = "test".to_string(); + }) + }) + + ``` + + use the HttpRequest to get the configuration like any other `Data` with `req.app_data::()` and set it with the `data()` method on the `resource` + + ```rust + #[derive(Default)] + struct ExtractorConfig { + config: String, + } + + impl FromRequest for YourExtractor { + type Error = Error; + type Future = Result; + type Config = ExtractorConfig; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let cfg = req.app_data::(); + println!("config data?: {:?}", cfg.unwrap().role); + ... + } + } + + App::new().service( + resource("/route_with_config") + .data(ExtractorConfig { + config: "test".to_string(), + }) + .route(post().to(handler_fn)), + ) + ``` + +- Resource registration. 1.0 version uses generalized resource + registration via `.service()` method. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.f(welcome)) + ``` + + use App's or Scope's `.service()` method. `.service()` method accepts + object that implements `HttpServiceFactory` trait. By default + actix-web provides `Resource` and `Scope` services. + + ```rust + App.new().service( + web::resource("/welcome") + .route(web::get().to(welcome)) + .route(web::post().to(post_handler)) + ``` + +- Scope registration. + + instead of + + ```rust + let app = App::new().scope("/{project_id}", |scope| { + scope + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) + .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) + }); + ``` + + use `.service()` for registration and `web::scope()` as scope object factory. + + ```rust + let app = App::new().service( + web::scope("/{project_id}") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .service(web::resource("/path2").to(|| HttpResponse::Ok())) + .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) + ); + ``` + +- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.with(welcome)) + ``` + + use `.to()` or `.to_async()` methods + + ```rust + App.new().service(web::resource("/welcome").to(welcome)) + ``` + +- Passing arguments to handler with extractors, multiple arguments are allowed + + instead of + + ```rust + fn welcome((body, req): (Bytes, HttpRequest)) -> ... { + ... + } + ``` + + use multiple arguments + + ```rust + fn welcome(body: Bytes, req: HttpRequest) -> ... { + ... + } + ``` + +- `.f()`, `.a()` and `.h()` handler registration methods have been removed. + Use `.to()` for handlers and `.to_async()` for async handlers. Handler function + must use extractors. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.f(welcome)) + ``` + + use App's `to()` or `to_async()` methods + + ```rust + App.new().service(web::resource("/welcome").to(welcome)) + ``` + +- `HttpRequest` does not provide access to request's payload stream. + + instead of + + ```rust + fn index(req: &HttpRequest) -> Box> { + req + .payload() + .from_err() + .fold((), |_, chunk| { + ... + }) + .map(|_| HttpResponse::Ok().finish()) + .responder() + } + ``` + + use `Payload` extractor + + ```rust + fn index(stream: web::Payload) -> impl Future { + stream + .from_err() + .fold((), |_, chunk| { + ... + }) + .map(|_| HttpResponse::Ok().finish()) + } + ``` + +- `State` is now `Data`. You register Data during the App initialization process + and then access it from handlers either using a Data extractor or using + HttpRequest's api. + + instead of + + ```rust + App.with_state(T) + ``` + + use App's `data` method + + ```rust + App.new() + .data(T) + ``` + + and either use the Data extractor within your handler + + ```rust + use actix_web::web::Data; + + fn endpoint_handler(Data)){ + ... + } + ``` + + .. or access your Data element from the HttpRequest + + ```rust + fn endpoint_handler(req: HttpRequest) { + let data: Option> = req.app_data::(); + } + ``` + +- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. + + instead of + + ```rust + use actix_web::AsyncResponder; + + fn endpoint_handler(...) -> impl Future{ + ... + .responder() + } + ``` + + .. simply omit AsyncResponder and the corresponding responder() finish method + +- Middleware + + instead of + + ```rust + let app = App::new() + .middleware(middleware::Logger::default()) + ``` + + use `.wrap()` method + + ```rust + let app = App::new() + .wrap(middleware::Logger::default()) + .route("/index.html", web::get().to(index)); + ``` + +- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` + method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. + + instead of + + ```rust + fn index(req: &HttpRequest) -> Responder { + req.body() + .and_then(|body| { + ... + }) + } + ``` + + use + + ```rust + fn index(body: Bytes) -> Responder { + ... + } + ``` + +- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type + +- StaticFiles and NamedFile have been moved to a separate crate. + + instead of `use actix_web::fs::StaticFile` + + use `use actix_files::Files` + + instead of `use actix_web::fs::Namedfile` + + use `use actix_files::NamedFile` + +- Multipart has been moved to a separate crate. + + instead of `use actix_web::multipart::Multipart` + + use `use actix_multipart::Multipart` + +- Response compression is not enabled by default. + To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. + +- Session middleware moved to actix-session crate + +- Actors support have been moved to `actix-web-actors` crate + +- Custom Error + + Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. + + Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError: + + ```rust + fn render_response(&self) -> HttpResponse { + self.error_response() + } + ``` diff --git a/actix-web/MIGRATION-2.0.md b/actix-web/MIGRATION-2.0.md new file mode 100644 index 000000000..0455062d1 --- /dev/null +++ b/actix-web/MIGRATION-2.0.md @@ -0,0 +1,48 @@ +# Migrating to 2.0.0 + +- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to + `.await` on `run` method result, in that case it awaits server exit. + +- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. + Stored data is available via `HttpRequest::app_data()` method at runtime. + +- Extractor configuration must be registered with `App::app_data()` instead of `App::data()` + +- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` + replace `fn` with `async fn` to convert sync handler to async + +- `actix_http_test::TestServer` moved to `actix_web::test` module. To start + test server use `test::start()` or `test_start_with_config()` methods + +- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders + http response. + +- Feature `rust-tls` renamed to `rustls` + + instead of + + ```rust + actix-web = { version = "2.0.0", features = ["rust-tls"] } + ``` + + use + + ```rust + actix-web = { version = "2.0.0", features = ["rustls"] } + ``` + +- Feature `ssl` renamed to `openssl` + + instead of + + ```rust + actix-web = { version = "2.0.0", features = ["ssl"] } + ``` + + use + + ```rust + actix-web = { version = "2.0.0", features = ["openssl"] } + ``` + +- `Cors` builder now requires that you call `.finish()` to construct the middleware diff --git a/actix-web/MIGRATION-3.0.md b/actix-web/MIGRATION-3.0.md new file mode 100644 index 000000000..54bcd58bd --- /dev/null +++ b/actix-web/MIGRATION-3.0.md @@ -0,0 +1,53 @@ +# Migrating to 3.0.0 + +- The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to + simply a `T`. To access a `Data` use `ServiceRequest::app_data::>()`. + +- Cookie handling has been offloaded to the `cookie` crate: + + - `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs. + - Some types now require lifetime parameters. + +- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects + any `actix-web` method previously expecting a time v0.1 input. + +- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now + result in `SameSite=None` being sent with the response Set-Cookie header. + To create a cookie without a SameSite attribute, remove any calls setting same_site. + +- actix-http support for Actors messages was moved to actix-http crate and is enabled + with feature `actors` + +- content_length function is removed from actix-http. + You can set Content-Length by normally setting the response body or calling no_chunking function. + +- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a + `u64` instead of a `usize`. + +- Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use + destructuring or `.into_inner()`. For example: + + ```rust + // Previously: + async fn some_route(path: web::Path<(String, String)>) -> String { + format!("Hello, {} {}", path.0, path.1) + } + + // Now (this also worked before): + async fn some_route(path: web::Path<(String, String)>) -> String { + let (first_name, last_name) = path.into_inner(); + format!("Hello, {} {}", first_name, last_name) + } + // Or (this wasn't previously supported): + async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String { + format!("Hello, {} {}", first_name, last_name) + } + ``` + +- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. + It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`, + or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`. + +- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. + +- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md new file mode 100644 index 000000000..e1448387a --- /dev/null +++ b/actix-web/MIGRATION-4.0.md @@ -0,0 +1,37 @@ +# Migrating to 4.0.0 + +> It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see [the historical migration notes](./MIGRATION-3.0.md). + +## Rustls Upgrade + +Required version of Rustls dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/) + +### `NormalizePath` middleware + +The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. + +#### Migration Diff + +```diff +- #[get("/test/")]` ++ #[get("/test")]` + +- .wrap(NormalizePath::default())` ++ .wrap(NormalizePath::trim())` +``` + +Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. + +### `FromRequest` trait + +The associated type `Config` of `FromRequest` was removed. + +### Compression Feature Flags + +Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are: + +- `compress-brotli` +- `compress-gzip` +- `compress-zstd` + +If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want to have compression enabled. diff --git a/actix-web/MIGRATION.md b/actix-web/MIGRATION.md deleted file mode 100644 index 338a04389..000000000 --- a/actix-web/MIGRATION.md +++ /dev/null @@ -1,677 +0,0 @@ -## Unreleased - -- The default `NormalizePath` behavior now strips trailing slashes by default. This was - previously documented to be the case in v3 but the behavior now matches. The effect is that - routes defined with trailing slashes will become inaccessible when - using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. - It is advised that the `new` method be used instead. - - Before: `#[get("/test/")]` - After: `#[get("/test")]` - - Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. - -- The `type Config` of `FromRequest` was removed. - -- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). - By default all compression algorithms are enabled. - To select algorithm you want to include with `middleware::Compress` use following flags: - - `compress-brotli` - - `compress-gzip` - - `compress-zstd` - If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want - to have compression enabled. Please change features selection like bellow: - - Before: `"compress"` - After: `"compress-brotli", "compress-gzip", "compress-zstd"` - - -## 3.0.0 - -- The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to - simply a `T`. To access a `Data` use `ServiceRequest::app_data::>()`. - -- Cookie handling has been offloaded to the `cookie` crate: - * `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs. - * Some types now require lifetime parameters. - -- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects - any `actix-web` method previously expecting a time v0.1 input. - -- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now - result in `SameSite=None` being sent with the response Set-Cookie header. - To create a cookie without a SameSite attribute, remove any calls setting same_site. - -- actix-http support for Actors messages was moved to actix-http crate and is enabled - with feature `actors` - -- content_length function is removed from actix-http. - You can set Content-Length by normally setting the response body or calling no_chunking function. - -- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a - `u64` instead of a `usize`. - -- Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use - destructuring or `.into_inner()`. For example: - - ```rust - // Previously: - async fn some_route(path: web::Path<(String, String)>) -> String { - format!("Hello, {} {}", path.0, path.1) - } - - // Now (this also worked before): - async fn some_route(path: web::Path<(String, String)>) -> String { - let (first_name, last_name) = path.into_inner(); - format!("Hello, {} {}", first_name, last_name) - } - // Or (this wasn't previously supported): - async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String { - format!("Hello, {} {}", first_name, last_name) - } - ``` - -- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. - It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`, - or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`. - -- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. - -- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. - - -## 2.0.0 - -- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to - `.await` on `run` method result, in that case it awaits server exit. - -- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. - Stored data is available via `HttpRequest::app_data()` method at runtime. - -- Extractor configuration must be registered with `App::app_data()` instead of `App::data()` - -- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` - replace `fn` with `async fn` to convert sync handler to async - -- `actix_http_test::TestServer` moved to `actix_web::test` module. To start - test server use `test::start()` or `test_start_with_config()` methods - -- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders - http response. - -- Feature `rust-tls` renamed to `rustls` - - instead of - - ```rust - actix-web = { version = "2.0.0", features = ["rust-tls"] } - ``` - - use - - ```rust - actix-web = { version = "2.0.0", features = ["rustls"] } - ``` - -- Feature `ssl` renamed to `openssl` - - instead of - - ```rust - actix-web = { version = "2.0.0", features = ["ssl"] } - ``` - - use - - ```rust - actix-web = { version = "2.0.0", features = ["openssl"] } - ``` -- `Cors` builder now requires that you call `.finish()` to construct the middleware - -## 1.0.1 - -- Cors middleware has been moved to `actix-cors` crate - - instead of - - ```rust - use actix_web::middleware::cors::Cors; - ``` - - use - - ```rust - use actix_cors::Cors; - ``` - -- Identity middleware has been moved to `actix-identity` crate - - instead of - - ```rust - use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService}; - ``` - - use - - ```rust - use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; - ``` - - -## 1.0.0 - -- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration - - instead of - - ```rust - - #[derive(Default)] - struct ExtractorConfig { - config: String, - } - - impl FromRequest for YourExtractor { - type Config = ExtractorConfig; - type Result = Result; - - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - println!("use the config: {:?}", cfg.config); - ... - } - } - - App::new().resource("/route_with_config", |r| { - r.post().with_config(handler_fn, |cfg| { - cfg.0.config = "test".to_string(); - }) - }) - - ``` - - use the HttpRequest to get the configuration like any other `Data` with `req.app_data::()` and set it with the `data()` method on the `resource` - - ```rust - #[derive(Default)] - struct ExtractorConfig { - config: String, - } - - impl FromRequest for YourExtractor { - type Error = Error; - type Future = Result; - type Config = ExtractorConfig; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let cfg = req.app_data::(); - println!("config data?: {:?}", cfg.unwrap().role); - ... - } - } - - App::new().service( - resource("/route_with_config") - .data(ExtractorConfig { - config: "test".to_string(), - }) - .route(post().to(handler_fn)), - ) - ``` - -- Resource registration. 1.0 version uses generalized resource - registration via `.service()` method. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.f(welcome)) - ``` - - use App's or Scope's `.service()` method. `.service()` method accepts - object that implements `HttpServiceFactory` trait. By default - actix-web provides `Resource` and `Scope` services. - - ```rust - App.new().service( - web::resource("/welcome") - .route(web::get().to(welcome)) - .route(web::post().to(post_handler)) - ``` - -- Scope registration. - - instead of - - ```rust - let app = App::new().scope("/{project_id}", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - }); - ``` - - use `.service()` for registration and `web::scope()` as scope object factory. - - ```rust - let app = App::new().service( - web::scope("/{project_id}") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .service(web::resource("/path2").to(|| HttpResponse::Ok())) - .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) - ); - ``` - -- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.with(welcome)) - ``` - - use `.to()` or `.to_async()` methods - - ```rust - App.new().service(web::resource("/welcome").to(welcome)) - ``` - -- Passing arguments to handler with extractors, multiple arguments are allowed - - instead of - - ```rust - fn welcome((body, req): (Bytes, HttpRequest)) -> ... { - ... - } - ``` - - use multiple arguments - - ```rust - fn welcome(body: Bytes, req: HttpRequest) -> ... { - ... - } - ``` - -- `.f()`, `.a()` and `.h()` handler registration methods have been removed. - Use `.to()` for handlers and `.to_async()` for async handlers. Handler function - must use extractors. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.f(welcome)) - ``` - - use App's `to()` or `to_async()` methods - - ```rust - App.new().service(web::resource("/welcome").to(welcome)) - ``` - -- `HttpRequest` does not provide access to request's payload stream. - - instead of - - ```rust - fn index(req: &HttpRequest) -> Box> { - req - .payload() - .from_err() - .fold((), |_, chunk| { - ... - }) - .map(|_| HttpResponse::Ok().finish()) - .responder() - } - ``` - - use `Payload` extractor - - ```rust - fn index(stream: web::Payload) -> impl Future { - stream - .from_err() - .fold((), |_, chunk| { - ... - }) - .map(|_| HttpResponse::Ok().finish()) - } - ``` - -- `State` is now `Data`. You register Data during the App initialization process - and then access it from handlers either using a Data extractor or using - HttpRequest's api. - - instead of - - ```rust - App.with_state(T) - ``` - - use App's `data` method - - ```rust - App.new() - .data(T) - ``` - - and either use the Data extractor within your handler - - ```rust - use actix_web::web::Data; - - fn endpoint_handler(Data)){ - ... - } - ``` - - .. or access your Data element from the HttpRequest - - ```rust - fn endpoint_handler(req: HttpRequest) { - let data: Option> = req.app_data::(); - } - ``` - - -- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. - - instead of - - ```rust - use actix_web::AsyncResponder; - - fn endpoint_handler(...) -> impl Future{ - ... - .responder() - } - ``` - - .. simply omit AsyncResponder and the corresponding responder() finish method - - -- Middleware - - instead of - - ```rust - let app = App::new() - .middleware(middleware::Logger::default()) - ``` - - use `.wrap()` method - - ```rust - let app = App::new() - .wrap(middleware::Logger::default()) - .route("/index.html", web::get().to(index)); - ``` - -- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` - method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. - - instead of - - ```rust - fn index(req: &HttpRequest) -> Responder { - req.body() - .and_then(|body| { - ... - }) - } - ``` - - use - - ```rust - fn index(body: Bytes) -> Responder { - ... - } - ``` - -- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type - -- StaticFiles and NamedFile have been moved to a separate crate. - - instead of `use actix_web::fs::StaticFile` - - use `use actix_files::Files` - - instead of `use actix_web::fs::Namedfile` - - use `use actix_files::NamedFile` - -- Multipart has been moved to a separate crate. - - instead of `use actix_web::multipart::Multipart` - - use `use actix_multipart::Multipart` - -- Response compression is not enabled by default. - To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. - -- Session middleware moved to actix-session crate - -- Actors support have been moved to `actix-web-actors` crate - -- Custom Error - - Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. - - Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError: - - ```rust - fn render_response(&self) -> HttpResponse { - self.error_response() - } - ``` - -## 0.7.15 - -- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in - your routes, you should use `%20`. - - instead of - - ```rust - fn main() { - let app = App::new().resource("/my index", |r| { - r.method(http::Method::GET) - .with(index); - }); - } - ``` - - use - - ```rust - fn main() { - let app = App::new().resource("/my%20index", |r| { - r.method(http::Method::GET) - .with(index); - }); - } - ``` - -- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` - - -## 0.7.4 - -- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - - -## 0.7 - -- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload - use `HttpMessage::payload()` method. - - instead of - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .from_err() - .fold(...) - .... - } - ``` - - use `.payload()` - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .payload() // <- get request payload stream - .from_err() - .fold(...) - .... - } - ``` - -- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&HttpRequest` instead of `&mut HttpRequest`. - -- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of - - ```rust - fn index(query: Query<..>, info: Json impl Responder {} - ``` - - use tuple of extractors and use `.with()` for registration: - - ```rust - fn index((query, json): (Query<..>, Json impl Responder {} - ``` - -- `Handler::handle()` uses `&self` instead of `&mut self` - -- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value - -- Removed deprecated `HttpServer::threads()`, use - [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - -- Renamed `client::ClientConnectorError::Connector` to - `client::ClientConnectorError::Resolver` - -- `Route::with()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_config()` - - instead of - - ```rust - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with(index) - .limit(4096); // <- limit size of the payload - }); - } - ``` - - use - - ```rust - - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with_config(index, |cfg| { // <- register handler - cfg.limit(4096); // <- limit size of the payload - }) - }); - } - ``` - -- `Route::with_async()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_async_config()` - - -## 0.6 - -- `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` - -- `ws::Message::Close` now includes optional close reason. - `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. - -- `HttpServer::threads()` renamed to `HttpServer::workers()`. - -- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. - Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. - -- `HttpRequest::extensions()` returns read only reference to the request's Extension - `HttpRequest::extensions_mut()` returns mutable reference. - -- Instead of - - `use actix_web::middleware::{ - CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage};` - - use `actix_web::middleware::session` - - `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, - RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - -- `FromRequest::from_request()` accepts mutable reference to a request - -- `FromRequest::Result` has to implement `Into>` - -- [`Responder::respond_to()`]( - https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) - is generic over `S` - -- Use `Query` extractor instead of HttpRequest::query()`. - - ```rust - fn index(q: Query>) -> Result<..> { - ... - } - ``` - - or - - ```rust - let q = Query::>::extract(req); - ``` - -- Websocket operations are implemented as `WsWriter` trait. - you need to use `use actix_web::ws::WsWriter` - - -## 0.5 - -- `HttpResponseBuilder::body()`, `.finish()`, `.json()` - methods return `HttpResponse` instead of `Result` - -- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` - moved to `actix_web::http` module - -- `actix_web::header` moved to `actix_web::http::header` - -- `NormalizePath` moved to `actix_web::http` module - -- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, - shortcut for `actix_web::server::HttpServer::new()` - -- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself - -- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. - -- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type - -- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` - functions should be used instead - -- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `Result<_, http::Error>` - -- `Application` renamed to a `App` - -- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` From 5b6cb681b9481f5db2cb56ff3789fcff11750985 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 03:09:33 +0000 Subject: [PATCH 344/861] update 4.0 migration guide --- actix-web/MIGRATION-4.0.md | 57 +++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e1448387a..e2517015b 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -1,32 +1,53 @@ # Migrating to 4.0.0 -> It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see [the historical migration notes](./MIGRATION-3.0.md). +It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder. -## Rustls Upgrade +Headings marked with :warning: are **breaking behavioral changes** and will probably not surface as compile-time errors. Automated tests _might_ detect their effects on your app. -Required version of Rustls dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/) +## Table of Contents: -### `NormalizePath` middleware +- [MSRV](#MSRV) +- [Module Structure](#Module-Structure) + +## MSRV + +The MSRV of Actix Web has been raised from 1.42 to 1.54. + +## Module Structure + +Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. + +## :warning: `NormalizePath` middleware The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. -#### Migration Diff +### Recommended Migration ```diff - #[get("/test/")]` + #[get("/test")]` + async fn handler() { -- .wrap(NormalizePath::default())` -+ .wrap(NormalizePath::trim())` + App::new() +- .wrap(NormalizePath::default())` ++ .wrap(NormalizePath::trim())` ``` Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. -### `FromRequest` trait +## `FromRequest` Trait -The associated type `Config` of `FromRequest` was removed. +The associated type `Config` of `FromRequest` was removed. If you have custom extractors, you can just remove this implementation and refer to config types directly, if required. -### Compression Feature Flags +### Recommended Migration + +```diff + impl FromRequest for MyExtractor { +- `type Config = ();` + } +``` + +## Compression Feature Flags Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are: @@ -35,3 +56,19 @@ Feature flag `compress` has been split into its supported algorithm (brotli, gzi - `compress-zstd` If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want to have compression enabled. + +## `web::Path` + +The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl. + +### Recommended Migration + +```diff +- async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) { ++ async fn handler(params: web::Path<(String, String)>) { ++ let (foo, bar) = params.into_inner(); +``` + +## Rustls Crate Upgrade + +Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/) From 391d8a744afe4329d883cc439c2a189503cbb7fe Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 03:13:11 +0000 Subject: [PATCH 345/861] update 4.0 migratio guide --- actix-web/MIGRATION-4.0.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e2517015b..edf26454b 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -6,8 +6,13 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob ## Table of Contents: -- [MSRV](#MSRV) -- [Module Structure](#Module-Structure) +- [MSRV](#msrv) +- [Module Structure](#module-structure) +- [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning) +- [`FromRequest` Trait](#fromrequest-trait) +- [Compression Feature Flags](#compression-feature-flags) +- [`web::Path`](#webpath) +- [Rustls](#rustls-crate-upgrade) ## MSRV @@ -17,7 +22,7 @@ The MSRV of Actix Web has been raised from 1.42 to 1.54. Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. -## :warning: `NormalizePath` middleware +## `NormalizePath` Middleware :warning: The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. From 075df88a07dcc158f40dc846839a662edae5f3ac Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 03:42:07 +0000 Subject: [PATCH 346/861] update 4.0 migration guide --- .prettierrc.json | 3 ++ actix-web/CHANGES.md | 14 ++++---- actix-web/MIGRATION-4.0.md | 66 ++++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 .prettierrc.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..677ba8ef2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "proseWrap": "never" +} diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 77918341a..826a7a1a0 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -292,7 +292,6 @@ ### Changed - Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] -[#2162]: (https://github.com/actix/actix-web/pull/2162) - `ServiceResponse::error_response` now uses body type of `Body`. [#2201] - `ServiceResponse::checked_expr` now returns a `Result`. [#2201] - Update `language-tags` to `0.3`. @@ -300,12 +299,13 @@ - `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] - All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] - All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] -- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuration parameter. [#2226] - `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] ### Removed - `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] +[#2162]: https://github.com/actix/actix-web/pull/2162 [#2200]: https://github.com/actix/actix-web/pull/2200 [#2201]: https://github.com/actix/actix-web/pull/2201 [#2253]: https://github.com/actix/actix-web/pull/2253 @@ -314,7 +314,7 @@ ## 4.0.0-beta.6 - 2021-04-17 ### Added -- `HttpResponse` and `HttpResponseBuilder` structs. [#2065] +- `HttpResponse` and `HttpResponseBuilder` types. [#2065] ### Changed - Most error types are now marked `#[non_exhaustive]`. [#2148] @@ -329,13 +329,13 @@ - `Header` extractor for extracting common HTTP headers in handlers. [#2094] - Added `TestServer::client_headers` method. [#2097] -### Fixed -- Double ampersand in Logger format is escaped correctly. [#2067] - ### Changed - `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. (Only the first error is kept when multiple error occur) [#2093] +### Fixed +- Double ampersand in Logger format is escaped correctly. [#2067] + ### Removed - The `client` mod was removed. Clients should now use `awc` directly. [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) @@ -422,7 +422,7 @@ - Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] ### Removed -- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now +- Public modules `middleware::{normalize, err_handlers}`. All necessary middleware types are now exposed directly by the `middleware` module. - Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index edf26454b..33f94150b 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -2,6 +2,8 @@ It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder. +This is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, are shown in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or PR. + Headings marked with :warning: are **breaking behavioral changes** and will probably not surface as compile-time errors. Automated tests _might_ detect their effects on your app. ## Table of Contents: @@ -26,8 +28,6 @@ Lots of modules has been organized in this release. If a compile error refers to The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. -### Recommended Migration - ```diff - #[get("/test/")]` + #[get("/test")]` @@ -44,8 +44,6 @@ Alternatively, explicitly require trailing slashes: `NormalizePath::new(Trailing The associated type `Config` of `FromRequest` was removed. If you have custom extractors, you can just remove this implementation and refer to config types directly, if required. -### Recommended Migration - ```diff impl FromRequest for MyExtractor { - `type Config = ();` @@ -66,8 +64,6 @@ If you have set in your `Cargo.toml` dedicated `actix-web` features and you stil The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl. -### Recommended Migration - ```diff - async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) { + async fn handler(params: web::Path<(String, String)>) { @@ -77,3 +73,61 @@ The inner field for `web::Path` was made private because It was causing too many ## Rustls Crate Upgrade Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/) + +## Removed `awc` Client Re-export + +Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` its own release cadence and prevents its own breaking changes from being blocked due to a re-export. + +```diff +- use actix_web::client::Client; ++ use awc::Client; +``` + +## Integration Testing Utils Moved to `actix-test` + +Actix Web's `test` module used to contain `TestServer`. Since this required the `awc` client and it was removed as a re-export (see above), it was moved to its own crate [`actix-test`](https://docs.rs/actix-test). + +```diff +- use use actix_web::test::start; ++ use use actix_test::start; +``` + +## Header APIs + +TODO + +## Body Types / Removal of Body+ResponseBody types / Addition of EitherBody + +TODO + +In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types. + +## Middleware Trait APIs + +TODO + +TODO: Also write the Middleware author's guide. + +## `Responder` Trait + +TODO + +## `App::data` deprecation + +TODO + +## It's probably not necessary to import `actix-rt` or `actix-service` any more + +TODO + +## Server must be awaited in order to run :warning: + +TODO + +## Guards API + +TODO + +## HttpResponse no longer implements Future + +TODO From 7fe800c3ff7cf5025aa93fd8e42a1062a6334d4a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 03:54:26 +0000 Subject: [PATCH 347/861] prepare actix-web release 4.0.0-rc.2 --- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 425475e41..531695c2f 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-rc.1" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-rc.1", default-features = false } +actix-web = { version = "4.0.0-rc.2", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.12" -actix-web = "4.0.0-rc.1" +actix-web = "4.0.0-rc.2" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 1e2f90249..47d44877a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] } actix-http = "3.0.0-rc.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d8458b2fc..2a340ec45 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.10", optional = true } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-rc.1" +actix-web = "4.0.0-rc.2" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 2c8a28acb..686d42ec8 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.1", default-features = false } +actix-web = { version = "4.0.0-rc.2", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 632092bbf..69821a6f2 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 10690102f..bb65abd3d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-rc.1" -actix-web = { version = "4.0.0-rc.1", default-features = false } +actix-web = { version = "4.0.0-rc.2", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 392d1cf8c..759916456 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.12" actix-utils = "3.0.0" -actix-web = "4.0.0-rc.1" +actix-web = "4.0.0-rc.2" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 826a7a1a0..b2c8dea85 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-rc.2 - 2022-02-02 ### Added - On-by-default `macros` feature flag to enable routing and runtime macros. [#2619] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index bdfdcb191..76ff5cfd0 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-rc.1" +version = "4.0.0-rc.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index f99a7be23..32276e81f 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.1)](https://docs.rs/actix-web/4.0.0-rc.1) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.2)](https://docs.rs/actix-web/4.0.0-rc.2) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.1) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.2)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e85eeb37a..089fdf88f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.1", features = ["openssl"] } +actix-web = { version = "4.0.0-rc.2", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" From fc5ecdc30bb1b964b512686bff3eaf83b7271cf5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Feb 2022 03:55:43 +0000 Subject: [PATCH 348/861] fix changelog --- actix-web/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index b2c8dea85..7ce282c8b 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -10,7 +10,7 @@ ### Removed - `rt::{Arbiter, ArbiterHandle}` re-exports. [#2619] -[#2601]: https://github.com/actix/actix-web/pull/2601 +[#2619]: https://github.com/actix/actix-web/pull/2619 ## 4.0.0-rc.1 - 2022-01-31 From 5ca42df89ae4c4eda46fcb408ba5b13549f09ad7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 3 Feb 2022 07:03:39 +0000 Subject: [PATCH 349/861] fix stuck connection when handler doesn't read payload (#2624) --- actix-http/CHANGES.md | 8 + actix-http/src/error.rs | 5 + actix-http/src/h1/codec.rs | 2 + actix-http/src/h1/decoder.rs | 8 +- actix-http/src/h1/dispatcher.rs | 73 +++++++- actix-http/src/h1/dispatcher_tests.rs | 238 ++++++++++++++++++++++++-- 6 files changed, 307 insertions(+), 27 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7f7af23a8..afc988d43 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,14 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624] + + +### Fixed +- Issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624] + +[#2624]: https://github.com/actix/actix-web/pull/2624 ## 3.0.0-rc.1 - 2022-01-31 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 841322861..52b953421 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -340,6 +340,7 @@ impl From for Error { /// A set of errors that can occur during dispatching HTTP requests. #[derive(Debug, Display, From)] +#[non_exhaustive] pub enum DispatchError { /// Service error. #[display(fmt = "Service Error")] @@ -373,6 +374,10 @@ pub enum DispatchError { #[display(fmt = "Connection shutdown timeout")] DisconnectTimeout, + /// Handler dropped payload before reading EOF. + #[display(fmt = "Handler dropped payload before reading EOF")] + HandlerDroppedPayload, + /// Internal error. #[display(fmt = "Internal error")] InternalError, diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index df74bcc42..80afd7455 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -125,11 +125,13 @@ impl Decoder for Codec { self.flags.set(Flags::HEAD, head.method == Method::HEAD); self.version = head.version; self.conn_type = head.connection_type(); + if self.conn_type == ConnectionType::KeepAlive && !self.flags.contains(Flags::KEEP_ALIVE_ENABLED) { self.conn_type = ConnectionType::Close } + match payload { PayloadType::None => self.payload = None, PayloadType::Payload(pl) => self.payload = Some(pl), diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index fa924f920..17b9b695c 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -209,15 +209,16 @@ impl MessageType for Request { let (len, method, uri, ver, h_len) = { // SAFETY: - // Create an uninitialized array of `MaybeUninit`. The `assume_init` is - // safe because the type we are claiming to have initialized here is a - // bunch of `MaybeUninit`s, which do not require initialization. + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the + // type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which + // do not require initialization. let mut parsed = unsafe { MaybeUninit::<[MaybeUninit>; MAX_HEADERS]>::uninit() .assume_init() }; let mut req = httparse::Request::new(&mut []); + match req.parse_with_uninit_headers(src, &mut parsed)? { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) @@ -232,6 +233,7 @@ impl MessageType for Request { (len, method, uri, version, req.headers.len()) } + httparse::Status::Partial => { return if src.len() >= MAX_BUFFER_SIZE { trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3f327171d..fbc7e5b99 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -21,7 +21,7 @@ use crate::{ config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - Error, Extensions, OnConnectData, Request, Response, StatusCode, + ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -151,7 +151,8 @@ pin_project! { error: Option, #[pin] - state: State, + pub(super) state: State, + // when Some(_) dispatcher is in state of receiving request payload payload: Option, messages: VecDeque, @@ -174,7 +175,7 @@ enum DispatcherMessage { pin_project! { #[project = StateProj] - enum State + pub(super) enum State where S: Service, X: Service, @@ -194,7 +195,7 @@ where X: Service, B: MessageBody, { - fn is_none(&self) -> bool { + pub(super) fn is_none(&self) -> bool { matches!(self, State::None) } } @@ -686,12 +687,74 @@ where let can_not_read = !self.can_read(cx); // limit amount of non-processed requests - if pipeline_queue_full || can_not_read { + if pipeline_queue_full { return Ok(false); } let mut this = self.as_mut().project(); + if can_not_read { + log::debug!("cannot read request payload"); + + if let Some(sender) = &this.payload { + // ...maybe handler does not want to read any more payload... + if let PayloadStatus::Dropped = sender.need_read(cx) { + log::debug!("handler dropped payload early; attempt to clean connection"); + // ...in which case poll request payload a few times + loop { + match this.codec.decode(this.read_buf)? { + Some(msg) => { + match msg { + // payload decoded did not yield EOF yet + Message::Chunk(Some(_)) => { + // if non-clean connection, next loop iter will detect empty + // read buffer and close connection + } + + // connection is in clean state for next request + Message::Chunk(None) => { + log::debug!("connection successfully cleaned"); + + // reset dispatcher state + let _ = this.payload.take(); + this.state.set(State::None); + + // break out of payload decode loop + break; + } + + // Either whole payload is read and loop is broken or more data + // was expected in which case connection is closed. In both + // situations dispatcher cannot get here. + Message::Item(_) => { + unreachable!("dispatcher is in payload receive state") + } + } + } + + // not enough info to decide if connection is going to be clean or not + None => { + log::error!( + "handler did not read whole payload and dispatcher could not \ + drain read buf; return 500 and close connection" + ); + + this.flags.insert(Flags::SHUTDOWN); + let mut res = Response::internal_server_error().drop_body(); + res.head_mut().set_connection_type(ConnectionType::Close); + this.messages.push_back(DispatcherMessage::Error(res)); + *this.error = Some(DispatchError::HandlerDroppedPayload); + return Ok(true); + } + } + } + } + } else { + // can_not_read and no request payload + return Ok(false); + } + } + let mut updated = false; loop { diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 891cce69c..40454d45a 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -1,6 +1,6 @@ use std::{future::Future, str, task::Poll, time::Duration}; -use actix_rt::time::sleep; +use actix_rt::{pin, time::sleep}; use actix_service::fn_service; use actix_utils::future::{ready, Ready}; use bytes::Bytes; @@ -53,6 +53,14 @@ fn echo_path_service( }) } +fn drop_payload_service( +) -> impl Service, Error = Error> { + fn_service(|mut req: Request| async move { + let _ = req.take_payload(); + Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped")) + }) +} + fn echo_payload_service() -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { @@ -89,7 +97,7 @@ async fn late_request() { None, OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); lazy(|cx| { assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -156,7 +164,7 @@ async fn oneshot_connection() { None, OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); lazy(|cx| { assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -173,13 +181,16 @@ async fn oneshot_connection() { stabilize_date_header(&mut res); let res = &res[..]; - let exp = b"\ - HTTP/1.1 200 OK\r\n\ - content-length: 5\r\n\ - connection: close\r\n\ - date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ - /abcd\ - "; + let exp = http_msg( + r" + HTTP/1.1 200 OK + content-length: 5 + connection: close + date: Thu, 01 Jan 1970 12:34:56 UTC + + /abcd + ", + ); assert_eq!( res, @@ -188,7 +199,7 @@ async fn oneshot_connection() { response: {:?}\n\ expected: {:?}", String::from_utf8_lossy(res), - String::from_utf8_lossy(exp) + String::from_utf8_lossy(&exp) ); }) .await; @@ -214,7 +225,7 @@ async fn keep_alive_timeout() { None, OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); lazy(|cx| { assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -293,7 +304,7 @@ async fn keep_alive_follow_up_req() { None, OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); lazy(|cx| { assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -413,7 +424,7 @@ async fn req_parse_err() { OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); match h1.as_mut().poll(cx) { Poll::Pending => panic!(), @@ -459,7 +470,7 @@ async fn pipelining_ok_then_ok() { OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -529,7 +540,7 @@ async fn pipelining_ok_then_bad() { OnConnectData::default(), ); - actix_rt::pin!(h1); + pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -601,7 +612,7 @@ async fn expect_handling() { ", ); - actix_rt::pin!(h1); + pin!(h1); assert!(h1.as_mut().poll(cx).is_pending()); assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -678,7 +689,7 @@ async fn expect_eager() { ", ); - actix_rt::pin!(h1); + pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); @@ -761,7 +772,7 @@ async fn upgrade_handling() { ", ); - actix_rt::pin!(h1); + pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); @@ -771,3 +782,192 @@ async fn upgrade_handling() { }) .await; } + +#[actix_rt::test] +async fn handler_drop_payload() { + let _ = env_logger::try_init(); + + let mut buf = TestBuffer::new(http_msg( + r" + POST /drop-payload HTTP/1.1 + Content-Length: 3 + + abc + ", + )); + + let services = HttpFlow::new( + drop_payload_service(), + ExpectHandler, + None::, + ); + + let h1 = Dispatcher::new( + buf.clone(), + services, + ServiceConfig::default(), + None, + OnConnectData::default(), + ); + pin!(h1); + + lazy(|cx| { + assert!(h1.as_mut().poll(cx).is_pending()); + + // polls: manual + assert_eq!(h1.poll_count, 1); + + let mut res = BytesMut::from(buf.take_write_buf().as_ref()); + stabilize_date_header(&mut res); + let res = &res[..]; + + let exp = http_msg( + r" + HTTP/1.1 200 OK + content-length: 15 + date: Thu, 01 Jan 1970 12:34:56 UTC + + payload dropped + ", + ); + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(&exp) + ); + + if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() { + assert!(inner.state.is_none()); + } + }) + .await; + + lazy(|cx| { + // add message that claims to have payload longer than provided + buf.extend_read_buf(http_msg( + r" + POST /drop-payload HTTP/1.1 + Content-Length: 200 + + abc + ", + )); + + assert!(h1.as_mut().poll(cx).is_pending()); + + // polls: manual => manual + assert_eq!(h1.poll_count, 2); + + let mut res = BytesMut::from(buf.take_write_buf().as_ref()); + stabilize_date_header(&mut res); + let res = &res[..]; + + // expect response immediately even though request side has not finished reading payload + let exp = http_msg( + r" + HTTP/1.1 200 OK + content-length: 15 + date: Thu, 01 Jan 1970 12:34:56 UTC + + payload dropped + ", + ); + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(&exp) + ); + }) + .await; + + lazy(|cx| { + assert!(h1.as_mut().poll(cx).is_ready()); + + // polls: manual => manual => manual + assert_eq!(h1.poll_count, 3); + + let mut res = BytesMut::from(buf.take_write_buf().as_ref()); + stabilize_date_header(&mut res); + let res = &res[..]; + + // expect that unrequested error response is sent back since connection could not be cleaned + let exp = http_msg( + r" + HTTP/1.1 500 Internal Server Error + content-length: 0 + connection: close + date: Thu, 01 Jan 1970 12:34:56 UTC + + ", + ); + + assert_eq!( + res, + exp, + "\nexpected response not in write buffer:\n\ + response: {:?}\n\ + expected: {:?}", + String::from_utf8_lossy(res), + String::from_utf8_lossy(&exp) + ); + }) + .await; +} + +fn http_msg(msg: impl AsRef) -> BytesMut { + let mut msg = msg + .as_ref() + .trim() + .split('\n') + .into_iter() + .map(|line| [line.trim_start(), "\r"].concat()) + .collect::>() + .join("\n"); + + // remove trailing \r + msg.pop(); + + if !msg.is_empty() && !msg.contains("\r\n\r\n") { + msg.push_str("\r\n\r\n"); + } + + BytesMut::from(msg.as_bytes()) +} + +#[test] +fn http_msg_creates_msg() { + assert_eq!(http_msg(r""), ""); + + assert_eq!( + http_msg( + r" + POST / HTTP/1.1 + Content-Length: 3 + + abc + " + ), + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc" + ); + + assert_eq!( + http_msg( + r" + GET / HTTP/1.1 + Content-Length: 3 + + " + ), + "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n" + ); +} From b4d3c2394d40212c52edb422d76945ee31fb2cd6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Feb 2022 18:22:38 +0000 Subject: [PATCH 350/861] clean up migration guide --- actix-web/MIGRATION-4.0.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 33f94150b..993bb7008 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -29,13 +29,13 @@ Lots of modules has been organized in this release. If a compile error refers to The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. ```diff -- #[get("/test/")]` -+ #[get("/test")]` +- #[get("/test/")] ++ #[get("/test")] async fn handler() { App::new() -- .wrap(NormalizePath::default())` -+ .wrap(NormalizePath::trim())` +- .wrap(NormalizePath::default()) ++ .wrap(NormalizePath::trim()) ``` Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. @@ -46,7 +46,7 @@ The associated type `Config` of `FromRequest` was removed. If you have custom ex ```diff impl FromRequest for MyExtractor { -- `type Config = ();` +- type Config = (); } ``` From b0a363a7ae154c795aa7c57ccb9e89d488aaec6e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Feb 2022 18:48:22 +0000 Subject: [PATCH 351/861] add migration note about fromrequest::configure --- actix-web/MIGRATION-4.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 993bb7008..202531c62 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -50,6 +50,8 @@ The associated type `Config` of `FromRequest` was removed. If you have custom ex } ``` +Consequently, the `FromRequest::configure` method was also removed. Config for extractors is still provided using `App::app_data` but should now be constructed in a standalone way. + ## Compression Feature Flags Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are: From 1d1a65282f9d9ad122fec27627e53a86f7d33304 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Feb 2022 20:37:33 +0000 Subject: [PATCH 352/861] RC refinements (#2625) --- actix-http/CHANGES.md | 5 ++++- actix-http/src/config.rs | 11 ++++++++--- actix-http/src/h1/encoder.rs | 8 ++++---- actix-http/src/responses/response.rs | 18 ++++++++++++++++++ actix-web/CHANGES.md | 5 +++++ actix-web/src/http/mod.rs | 2 +- actix-web/src/response/responder.rs | 1 + 7 files changed, 41 insertions(+), 9 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index afc988d43..c28013853 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,14 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- Implement `From>` for `Response>`. [#2625] + ### Changed - `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624] - ### Fixed - Issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624] [#2624]: https://github.com/actix/actix-web/pull/2624 +[#2625]: https://github.com/actix/actix-web/pull/2625 ## 3.0.0-rc.1 - 2022-01-31 diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 8045910be..ac95a2802 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -104,8 +104,13 @@ impl ServiceConfig { self.0.date_service.now() } - pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) { - let mut buf: [u8; 39] = [0; 39]; + /// Writes date header to `dst` buffer. + /// + /// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls + /// than normal. Note that a CRLF (`\r\n`) is included in what is written. + #[doc(hidden)] + pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) { + let mut buf: [u8; 37] = [0; 37]; buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); @@ -113,7 +118,7 @@ impl ServiceConfig { .date_service .with_date(|date| buf[6..35].copy_from_slice(&date.bytes)); - buf[35..].copy_from_slice(b"\r\n\r\n"); + buf[35..].copy_from_slice(b"\r\n"); dst.extend_from_slice(&buf); } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index a24ba5911..ba98f4641 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -210,14 +210,14 @@ pub(crate) trait MessageType: Sized { dst.advance_mut(pos); } - // optimized date header, set_date writes \r\n if !has_date { + // optimized date header, write_date_header writes its own \r\n config.write_date_header(dst, camel_case); - } else { - // msg eof - dst.extend_from_slice(b"\r\n"); } + // end-of-headers marker + dst.extend_from_slice(b"\r\n"); + Ok(()) } diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index da5503c1c..ceb158f65 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -285,6 +285,24 @@ impl From<&'static [u8]> for Response<&'static [u8]> { } } +impl From> for Response> { + fn from(val: Vec) -> Self { + let mut res = Response::with_body(StatusCode::OK, val); + let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res + } +} + +impl From<&Vec> for Response> { + fn from(val: &Vec) -> Self { + let mut res = Response::with_body(StatusCode::OK, val.clone()); + let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap(); + res.headers_mut().insert(header::CONTENT_TYPE, mime); + res + } +} + impl From for Response { fn from(val: String) -> Self { let mut res = Response::with_body(StatusCode::OK, val); diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 7ce282c8b..37fe6ca6a 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +- Implement `Responder` for `Vec`. [#2625] +- Re-export `KeepAlive` in `http` mod. [#2625] + +[#2625]: https://github.com/actix/actix-web/pull/2625 ## 4.0.0-rc.2 - 2022-02-02 diff --git a/actix-web/src/http/mod.rs b/actix-web/src/http/mod.rs index 2581532cd..91c0ca377 100644 --- a/actix-web/src/http/mod.rs +++ b/actix-web/src/http/mod.rs @@ -3,4 +3,4 @@ pub mod header; // TODO: figure out how best to expose http::Error vs actix_http::Error -pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version}; +pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version}; diff --git a/actix-web/src/response/responder.rs b/actix-web/src/response/responder.rs index d1b9e49e0..cb71369cf 100644 --- a/actix-web/src/response/responder.rs +++ b/actix-web/src/response/responder.rs @@ -132,6 +132,7 @@ macro_rules! impl_responder_by_forward_into_base_response { } impl_responder_by_forward_into_base_response!(&'static [u8]); +impl_responder_by_forward_into_base_response!(Vec); impl_responder_by_forward_into_base_response!(Bytes); impl_responder_by_forward_into_base_response!(BytesMut); From b653bf557f191683896829ae682343e345778b78 Mon Sep 17 00:00:00 2001 From: Darin Gordon Date: Mon, 7 Feb 2022 14:04:03 -0500 Subject: [PATCH 353/861] added note to v4 migration guide about worker thread update (#2634) --- actix-web/MIGRATION-4.0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 202531c62..65f638c2e 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -9,6 +9,7 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob ## Table of Contents: - [MSRV](#msrv) +- [Server Settings](#server-settings) - [Module Structure](#module-structure) - [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning) - [`FromRequest` Trait](#fromrequest-trait) @@ -20,6 +21,11 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob The MSRV of Actix Web has been raised from 1.42 to 1.54. +## Server Settings + +Until actix-web v4, actix-server used the total number of available logical cores as the default number of worker threads. The new default number of worker threads for actix-server is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654cea5e55b169f6fd05693b765799733b1b#diff-96893e8cb2125e6eefc96105a8462c4fd834943ef5129ffbead1a114133ebb78). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957). + + ## Module Structure Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. From b0fbe0dfd8da9ef09b5d3043f49f6bd11d9a2407 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 06:58:26 +0000 Subject: [PATCH 354/861] fix workers doc --- actix-web/MIGRATION-4.0.md | 4 ++++ actix-web/src/server.rs | 2 +- scripts/unreleased | 13 +++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 65f638c2e..f6c2f9bc6 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -139,3 +139,7 @@ TODO ## HttpResponse no longer implements Future TODO + +## `#[actix_web::main]` and `#[tokio::main]` + +TODO diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index c9d9cc9bd..bdcfbf48a 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -128,7 +128,7 @@ where /// Set number of workers to start. /// - /// By default, server uses number of available logical CPU as thread count. + /// By default, the number of available physical CPUs is used as the worker count. pub fn workers(mut self, num: usize) -> Self { self.builder = self.builder.workers(num); self diff --git a/scripts/unreleased b/scripts/unreleased index 4dfa2d9ae..e664c0879 100755 --- a/scripts/unreleased +++ b/scripts/unreleased @@ -9,7 +9,16 @@ unreleased_for() { DIR=$1 CARGO_MANIFEST=$DIR/Cargo.toml - CHANGELOG_FILE=$DIR/CHANGES.md + + # determine changelog file name + if [ -f "$DIR/CHANGES.md" ]; then + CHANGELOG_FILE=$DIR/CHANGES.md + elif [ -f "$DIR/CHANGELOG.md" ]; then + CHANGELOG_FILE=$DIR/CHANGELOG.md + else + echo "No changelog file found" + exit 1 + fi # get current version PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" @@ -36,6 +45,6 @@ unreleased_for() { cat "$CHANGE_CHUNK_FILE" } -for f in $(fd --absolute-path CHANGES.md); do +for f in $(fd --absolute-path 'CHANGE\w+.md'); do unreleased_for $(dirname $f) done From 0c144054cba2fee55bad6183865b98f3d96a74a2 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Tue, 8 Feb 2022 10:50:05 +0300 Subject: [PATCH 355/861] make `Condition` generic over body type (#2635) Co-authored-by: Rob Ede --- actix-web/CHANGES.md | 4 + actix-web/src/middleware/condition.rs | 120 ++++++++++++++++++-------- 2 files changed, 88 insertions(+), 36 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 37fe6ca6a..78fd50c8c 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,11 +1,15 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635] + ### Added - Implement `Responder` for `Vec`. [#2625] - Re-export `KeepAlive` in `http` mod. [#2625] [#2625]: https://github.com/actix/actix-web/pull/2625 +[#2635]: https://github.com/actix/actix-web/pull/2635 ## 4.0.0-rc.2 - 2022-02-02 diff --git a/actix-web/src/middleware/condition.rs b/actix-web/src/middleware/condition.rs index 659f88bc9..65f25a67c 100644 --- a/actix-web/src/middleware/condition.rs +++ b/actix-web/src/middleware/condition.rs @@ -1,18 +1,22 @@ //! For middleware documentation, see [`Condition`]. -use std::task::{Context, Poll}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; -use actix_service::{Service, Transform}; -use actix_utils::future::Either; -use futures_core::future::LocalBoxFuture; +use futures_core::{future::LocalBoxFuture, ready}; use futures_util::future::FutureExt as _; +use pin_project_lite::pin_project; + +use crate::{ + body::EitherBody, + dev::{Service, ServiceResponse, Transform}, +}; /// Middleware for conditionally enabling other middleware. /// -/// The controlled middleware must not change the `Service` interfaces. This means you cannot -/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat) -/// middleware for a workaround. -/// /// # Examples /// ``` /// use actix_web::middleware::{Condition, NormalizePath}; @@ -36,16 +40,16 @@ impl Condition { } } -impl Transform for Condition +impl Transform for Condition where - S: Service + 'static, - T: Transform, + S: Service, Error = Err> + 'static, + T: Transform, Error = Err>, T::Future: 'static, T::InitError: 'static, T::Transform: 'static, { - type Response = S::Response; - type Error = S::Error; + type Response = ServiceResponse>; + type Error = Err; type Transform = ConditionMiddleware; type InitError = T::InitError; type Future = LocalBoxFuture<'static, Result>; @@ -69,14 +73,14 @@ pub enum ConditionMiddleware { Disable(D), } -impl Service for ConditionMiddleware +impl Service for ConditionMiddleware where - E: Service, - D: Service, + E: Service, Error = Err>, + D: Service, Error = Err>, { - type Response = E::Response; - type Error = E::Error; - type Future = Either; + type Response = ServiceResponse>; + type Error = Err; + type Future = ConditionMiddlewareFuture; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { match self { @@ -87,27 +91,59 @@ where 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)), + ConditionMiddleware::Enable(service) => ConditionMiddlewareFuture::Enabled { + fut: service.call(req), + }, + ConditionMiddleware::Disable(service) => ConditionMiddlewareFuture::Disabled { + fut: service.call(req), + }, } } } +pin_project! { + #[doc(hidden)] + #[project = ConditionProj] + pub enum ConditionMiddlewareFuture { + Enabled { #[pin] fut: E, }, + Disabled { #[pin] fut: D, }, + } +} + +impl Future for ConditionMiddlewareFuture +where + E: Future, Err>>, + D: Future, Err>>, +{ + type Output = Result>, Err>; + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let res = match self.project() { + ConditionProj::Enabled { fut } => ready!(fut.poll(cx))?.map_into_left_body(), + ConditionProj::Disabled { fut } => ready!(fut.poll(cx))?.map_into_right_body(), + }; + + Poll::Ready(Ok(res)) + } +} + #[cfg(test)] mod tests { - use actix_service::IntoService; - use actix_utils::future::ok; + use actix_service::IntoService as _; use super::*; use crate::{ + body::BoxBody, dev::{ServiceRequest, ServiceResponse}, error::Result, http::{ header::{HeaderValue, CONTENT_TYPE}, StatusCode, }, - middleware::{err_handlers::*, Compat}, + middleware::{self, ErrorHandlerResponse, ErrorHandlers}, test::{self, TestRequest}, + web::Bytes, HttpResponse, }; @@ -120,40 +156,52 @@ mod tests { Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } + #[test] + fn compat_with_builtin_middleware() { + let _ = Condition::new(true, middleware::Compat::noop()); + let _ = Condition::new(true, middleware::Logger::default()); + let _ = Condition::new(true, middleware::Compress::default()); + let _ = Condition::new(true, middleware::NormalizePath::trim()); + let _ = Condition::new(true, middleware::DefaultHeaders::new()); + let _ = Condition::new(true, middleware::ErrorHandlers::::new()); + let _ = Condition::new(true, middleware::ErrorHandlers::::new()); + } + #[actix_rt::test] async fn test_handler_enabled() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) + let srv = |req: ServiceRequest| async move { + let resp = HttpResponse::InternalServerError().message_body(String::new())?; + Ok(req.into_response(resp)) }; - let mw = Compat::new( - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ); + let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mw = Condition::new(true, mw) .new_transform(srv.into_service()) .await .unwrap(); - let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; + + let resp: ServiceResponse, String>> = + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } #[actix_rt::test] async fn test_handler_disabled() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) + let srv = |req: ServiceRequest| async move { + let resp = HttpResponse::InternalServerError().message_body(String::new())?; + Ok(req.into_response(resp)) }; - let mw = Compat::new( - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ); + let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mw = Condition::new(false, mw) .new_transform(srv.into_service()) .await .unwrap(); - let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp: ServiceResponse, String>> = + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } From 3d621677a57a32f73d8346aeccd8968cd14f54a9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 08:00:47 +0000 Subject: [PATCH 356/861] clippy --- actix-files/src/directory.rs | 2 +- actix-http/src/h1/client.rs | 5 ++++- actix-router/benches/router.rs | 5 +++-- actix-router/src/resource.rs | 2 +- actix-web/src/http/header/if_none_match.rs | 8 ++++---- awc/src/client/connection.rs | 10 +++++----- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 26225ea5c..32dd6365b 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -75,7 +75,7 @@ pub(crate) fn directory_listing( if dir.is_visible(&entry) { let entry = entry.unwrap(); let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"), + Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"), Ok(p) => base.join(p).to_string_lossy().into_owned(), Err(_) => continue, }; diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 4e0ae8f48..75c88d00c 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -128,7 +128,10 @@ impl Decoder for ClientCodec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); + debug_assert!( + self.inner.payload.is_none(), + "Payload decoder should not be set" + ); if let Some((req, payload)) = self.inner.decoder.decode(src)? { if let Some(conn_type) = req.conn_type() { diff --git a/actix-router/benches/router.rs b/actix-router/benches/router.rs index a428b9f13..6f6b67b48 100644 --- a/actix-router/benches/router.rs +++ b/actix-router/benches/router.rs @@ -145,7 +145,8 @@ macro_rules! register { concat!("/user/keys"), concat!("/user/keys/", $p1), ]; - std::array::IntoIter::new(arr) + + IntoIterator::into_iter(arr) }}; } @@ -158,7 +159,7 @@ fn call() -> impl Iterator { "/repos/rust-lang/rust/releases/1.51.0", ]; - std::array::IntoIter::new(arr) + IntoIterator::into_iter(arr) } fn compare_routers(c: &mut Criterion) { diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index f3eaa9f42..c616b467a 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -898,7 +898,7 @@ impl ResourceDef { } let pattern_re_set = RegexSet::new(re_set).unwrap(); - let segments = segments.unwrap_or_else(Vec::new); + let segments = segments.unwrap_or_default(); ( PatternType::DynamicSet(pattern_re_set, pattern_data), diff --git a/actix-web/src/http/header/if_none_match.rs b/actix-web/src/http/header/if_none_match.rs index 863be70cf..86d7da9b2 100644 --- a/actix-web/src/http/header/if_none_match.rs +++ b/actix-web/src/http/header/if_none_match.rs @@ -62,18 +62,18 @@ crate::http::header::common_header! { #[cfg(test)] mod tests { + use actix_http::test::TestRequest; + use super::IfNoneMatch; use crate::http::header::{EntityTag, Header, IF_NONE_MATCH}; - use actix_http::test::TestRequest; #[test] fn test_if_none_match() { - let mut if_none_match: Result; - let req = TestRequest::default() .insert_header((IF_NONE_MATCH, "*")) .finish(); - if_none_match = Header::parse(&req); + + let mut if_none_match = IfNoneMatch::parse(&req); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); let req = TestRequest::default() diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs index 456f119aa..9de4ece4f 100644 --- a/awc/src/client/connection.rs +++ b/awc/src/client/connection.rs @@ -337,7 +337,7 @@ where match self.get_mut() { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf), Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf), - _ => unreachable!(H2_UNREACHABLE_WRITE), + _ => unreachable!("{}", H2_UNREACHABLE_WRITE), } } @@ -345,7 +345,7 @@ where match self.get_mut() { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx), Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx), - _ => unreachable!(H2_UNREACHABLE_WRITE), + _ => unreachable!("{}", H2_UNREACHABLE_WRITE), } } @@ -353,7 +353,7 @@ where match self.get_mut() { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx), Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx), - _ => unreachable!(H2_UNREACHABLE_WRITE), + _ => unreachable!("{}", H2_UNREACHABLE_WRITE), } } @@ -369,7 +369,7 @@ where Connection::Tls(ConnectionType::H1(conn)) => { Pin::new(conn).poll_write_vectored(cx, bufs) } - _ => unreachable!(H2_UNREACHABLE_WRITE), + _ => unreachable!("{}", H2_UNREACHABLE_WRITE), } } @@ -377,7 +377,7 @@ where match *self { Connection::Tcp(ConnectionType::H1(ref conn)) => conn.is_write_vectored(), Connection::Tls(ConnectionType::H1(ref conn)) => conn.is_write_vectored(), - _ => unreachable!(H2_UNREACHABLE_WRITE), + _ => unreachable!("{}", H2_UNREACHABLE_WRITE), } } } From 161861997c1f3516cc451bb115e87578eebad5d9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 09:31:20 +0000 Subject: [PATCH 357/861] prepare actix-http release 3.0.0-rc.2 --- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 531695c2f..f1ea3979f 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-rc.1" +actix-http = "3.0.0-rc.2" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-rc.2", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 47d44877a..7ced53bee 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-rc.1" +actix-http = "3.0.0-rc.2" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c28013853..e9191b0fe 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-rc.2 - 2022-02-08 ### Added - Implement `From>` for `Response>`. [#2625] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2a340ec45..da55d212c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-rc.1" +version = "3.0.0-rc.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 0087fabe3..d06db8422 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.1)](https://docs.rs/actix-http/3.0.0-rc.1) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.2)](https://docs.rs/actix-http/3.0.0-rc.2) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.1) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.2) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 686d42ec8..e141dea78 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-rc.1" +actix-http = "3.0.0-rc.2" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 69821a6f2..0083c5ac8 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.4.1" -actix-http = "3.0.0-rc.1" +actix-http = "3.0.0-rc.2" actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index bb65abd3d..08bae25e0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" -actix-http = "3.0.0-rc.1" +actix-http = "3.0.0-rc.2" actix-web = { version = "4.0.0-rc.2", default-features = false } bytes = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 76ff5cfd0..038c65857 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,7 +71,7 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" actix-web-codegen = { version = "0.5.0-rc.2", optional = true } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 089fdf88f..93c0c5f9e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" -actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-rc.1", features = ["openssl"] } +actix-http = { version = "3.0.0-rc.2", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } From 593fbde46ab169362a3dad6aeedbb6fda4d542ba Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 09:31:48 +0000 Subject: [PATCH 358/861] prepare actix-web release 4.0.0-rc.3 --- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- awc/Cargo.toml | 2 +- 11 files changed, 15 insertions(+), 12 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f1ea3979f..08bd2f359 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] actix-http = "3.0.0-rc.2" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-rc.2", default-features = false } +actix-web = { version = "4.0.0-rc.3", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.12" -actix-web = "4.0.0-rc.2" +actix-web = "4.0.0-rc.3" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 7ced53bee..e9986ef81 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } actix-http = "3.0.0-rc.2" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index da55d212c..ba8f5fa1d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.10", optional = true } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3.0.0", features = ["openssl"] } -actix-web = "4.0.0-rc.2" +actix-web = "4.0.0-rc.3" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e141dea78..414c28862 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.2", default-features = false } +actix-web = { version = "4.0.0-rc.3", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 0083c5ac8..0f8aff074 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -34,7 +34,7 @@ actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 08bae25e0..0499e19e4 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" actix = { version = "0.12.0", default-features = false } actix-codec = "0.4.1" actix-http = "3.0.0-rc.2" -actix-web = { version = "4.0.0-rc.2", default-features = false } +actix-web = { version = "4.0.0-rc.3", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 759916456..d5492243e 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.12" actix-utils = "3.0.0" -actix-web = "4.0.0-rc.2" +actix-web = "4.0.0-rc.3" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 78fd50c8c..ba29cdaa2 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-rc.3 - 2022-02-08 ### Changed - `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 038c65857..9ab7756f3 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-rc.2" +version = "4.0.0-rc.3" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index 32276e81f..2a87d340d 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.2)](https://docs.rs/actix-web/4.0.0-rc.2) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.3)](https://docs.rs/actix-web/4.0.0-rc.3) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.2) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.3/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.3)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 93c0c5f9e..e9cc5d656 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -99,7 +99,7 @@ actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.2", features = ["openssl"] } +actix-web = { version = "4.0.0-rc.3", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" From 074d18209da71f0bb0f5b348e3ab26a4e80e1924 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 10:21:47 +0000 Subject: [PATCH 359/861] better document relationship with tokio --- actix-web-codegen/src/lib.rs | 4 +++ actix-web/README.md | 2 +- actix-web/src/handler.rs | 6 +++- actix-web/src/lib.rs | 41 +++++++++++++------------ actix-web/src/rt.rs | 59 ++++++++++++++++++++++++++++-------- 5 files changed, 78 insertions(+), 34 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index f41e1ce38..5ca5616b6 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -152,6 +152,10 @@ method_macro!(Patch, patch); /// Marks async main function as the Actix Web system entry-point. /// +/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is +/// still necessary for actor support (since actors use a `System`). Read more in the +/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs. +/// /// # Examples /// ``` /// #[actix_web::main] diff --git a/actix-web/README.md b/actix-web/README.md index 2a87d340d..4adeb3910 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -32,7 +32,7 @@ - Static assets - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -- Includes an async [HTTP client](https://docs.rs/awc/) +- Integrates with the [`awc` HTTP client](https://docs.rs/awc/) - Runs on stable Rust 1.54+ ## Documentation diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs index 7eb70ed25..cf86cb38b 100644 --- a/actix-web/src/handler.rs +++ b/actix-web/src/handler.rs @@ -10,12 +10,16 @@ use crate::{ /// The interface for request handlers. /// /// # What Is A Request Handler -/// A request handler has three requirements: +/// In short, a handler is just an async function that receives request-based arguments, in any +/// order, and returns something that can be converted to a response. +/// +/// In particular, a request handler has three requirements: /// 1. It is an async function (or a function/closure that returns an appropriate future); /// 1. The function parameters (up to 12) implement [`FromRequest`]; /// 1. The async function (or future) resolves to a type that can be converted into an /// [`HttpResponse`] (i.e., it implements the [`Responder`] trait). /// +/// /// # Compiler Errors /// If you get the error `the trait Handler<_> is not implemented`, then your handler does not /// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index c3313db81..34bee7529 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -42,28 +42,29 @@ //! and otherwise utilizing them. //! //! # Features -//! * Supports *HTTP/1.x* and *HTTP/2* -//! * Streaming and pipelining -//! * Keep-alive and slow requests handling -//! * Client/server [WebSockets](https://actix.rs/docs/websockets/) support -//! * Transparent content compression/decompression (br, gzip, deflate, zstd) -//! * Powerful [request routing](https://actix.rs/docs/url-dispatch/) -//! * Multipart streams -//! * Static assets -//! * SSL support using OpenSSL or Rustls -//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -//! * Includes an async [HTTP client](https://docs.rs/awc/) -//! * Runs on stable Rust 1.54+ +//! - Supports HTTP/1.x and HTTP/2 +//! - Streaming and pipelining +//! - Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros +//! - Full [Tokio](https://tokio.rs) compatibility +//! - Keep-alive and slow requests handling +//! - Client/server [WebSockets](https://actix.rs/docs/websockets/) support +//! - Transparent content compression/decompression (br, gzip, deflate, zstd) +//! - Multipart streams +//! - Static assets +//! - SSL support using OpenSSL or Rustls +//! - Middlewares ([Logger, Session, CORS, etc](middleware)) +//! - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) +//! - Runs on stable Rust 1.54+ //! //! # Crate Features -//! * `cookies` - cookies support (enabled by default) -//! * `macros` - routing and runtime macros (enabled by default) -//! * `compress-brotli` - brotli content encoding compression support (enabled by default) -//! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default) -//! * `compress-zstd` - zstd content encoding compression support (enabled by default) -//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` -//! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` -//! * `secure-cookies` - secure cookies support +//! - `cookies` - cookies support (enabled by default) +//! - `macros` - routing and runtime macros (enabled by default) +//! - `compress-brotli` - brotli content encoding compression support (enabled by default) +//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default) +//! - `compress-zstd` - zstd content encoding compression support (enabled by default) +//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` +//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` +//! - `secure-cookies` - secure cookies support #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs index efe9fdfe6..3b4f21b46 100644 --- a/actix-web/src/rt.rs +++ b/actix-web/src/rt.rs @@ -1,9 +1,10 @@ -//! A selection of re-exports from [`actix-rt`] and [`tokio`]. +//! A selection of re-exports from [`tokio`] and [`actix-rt`]. //! -//! [`actix-rt`]: https://docs.rs/actix_rt -//! [`tokio`]: https://docs.rs/tokio +//! Actix Web runs on [Tokio], providing full[^compat] compatibility with its huge ecosystem of +//! crates. Each of the server's workers uses a single-threaded runtime. Read more about the +//! architecture in [`actix-rt`]'s docs. //! -//! # Running Actix Web Macro-less +//! # Running Actix Web Without Macros //! ```no_run //! use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; //! @@ -12,19 +13,53 @@ //! "Hello world!\r\n" //! } //! -//! # fn main() -> std::io::Result<()> { -//! rt::System::new().block_on( +//! fn main() -> std::io::Result<()> { +//! rt::System::new().block_on( +//! HttpServer::new(|| { +//! App::new().service(web::resource("/").route(web::get().to(index))) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! ) +//! } +//! ``` +//! +//! # Running Actix Web Using `#[tokio::main]` +//! If you need to run something alongside Actix Web that uses Tokio's work stealing functionality, +//! you can run Actix Web under `#[tokio::main]`. The [`Server`](crate::dev::Server) object returned +//! from [`HttpServer::run`](crate::HttpServer::run) can also be [`spawn`]ed, if preferred. +//! +//! Note that `actix` actor support (and therefore WebSocket support through `actix-web-actors`) +//! still require `#[actix_web::main]` since they require a [`System`] to be set up. +//! +//! ```no_run +//! use actix_web::{get, middleware, rt, web, App, HttpRequest, HttpServer}; +//! +//! #[get("/")] +//! async fn index(req: HttpRequest) -> &'static str { +//! println!("REQ: {:?}", req); +//! "Hello world!\r\n" +//! } +//! +//! #[tokio::main] +//! async fn main() -> std::io::Result<()> { //! HttpServer::new(|| { -//! App::new() -//! .wrap(middleware::Logger::default()) -//! .service(web::resource("/").route(web::get().to(index))) +//! App::new().service(index) //! }) //! .bind(("127.0.0.1", 8080))? -//! .workers(1) //! .run() -//! ) -//! # } +//! .await +//! } //! ``` +//! +//! [^compat]: Crates that use Tokio's [`block_in_place`] will not work with Actix Web. Fortunately, +//! the vast majority of Tokio-based crates do not use it. +//! +//! [`actix-rt`]: https://docs.rs/actix_rt +//! [`tokio`]: https://docs.rs/tokio +//! [Tokio]: https://docs.rs/tokio +//! [`spawn`]: https://docs.rs/tokio/1/tokio/fn.spawn.html +//! [`block_in_place`]: https://docs.rs/tokio/1/tokio/task/fn.block_in_place.html // In particular: // - Omit the `Arbiter` types because they have limited value here. From 3f2db9e75ccd18f5936b948aa482af02ce02f39f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 12:25:13 +0000 Subject: [PATCH 360/861] fix doc tests --- actix-web/Cargo.toml | 1 + actix-web/src/rt.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 9ab7756f3..17b2f2356 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -116,6 +116,7 @@ serde = { version = "1.0", features = ["derive"] } static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } +tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } zstd = "0.10" [[test]] diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs index 3b4f21b46..929eadfd8 100644 --- a/actix-web/src/rt.rs +++ b/actix-web/src/rt.rs @@ -55,7 +55,7 @@ //! [^compat]: Crates that use Tokio's [`block_in_place`] will not work with Actix Web. Fortunately, //! the vast majority of Tokio-based crates do not use it. //! -//! [`actix-rt`]: https://docs.rs/actix_rt +//! [`actix-rt`]: https://docs.rs/actix-rt //! [`tokio`]: https://docs.rs/tokio //! [Tokio]: https://docs.rs/tokio //! [`spawn`]: https://docs.rs/tokio/1/tokio/fn.spawn.html From 98faa61afe437a5e79d728adfa7d3b4b6ecdf7d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 13:37:01 +0000 Subject: [PATCH 361/861] fix impl assertions --- actix-http/src/body/body_stream.rs | 8 ++++---- actix-http/src/body/boxed.rs | 4 ++-- actix-http/src/body/sized_stream.rs | 8 ++++---- awc/src/any_body.rs | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs index cf4f488b2..5a12c1e40 100644 --- a/actix-http/src/body/body_stream.rs +++ b/actix-http/src/body/body_stream.rs @@ -80,7 +80,7 @@ mod tests { use futures_core::ready; use futures_util::{stream, FutureExt as _}; use pin_project_lite::pin_project; - use static_assertions::{assert_impl_all, assert_not_impl_all}; + use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; use crate::body::to_bytes; @@ -91,10 +91,10 @@ mod tests { assert_impl_all!(BodyStream>>: MessageBody); assert_impl_all!(BodyStream>>: MessageBody); - assert_not_impl_all!(BodyStream>: MessageBody); - assert_not_impl_all!(BodyStream>: MessageBody); + assert_not_impl_any!(BodyStream>: MessageBody); + assert_not_impl_any!(BodyStream>: MessageBody); // crate::Error is not Clone - assert_not_impl_all!(BodyStream>>: MessageBody); + assert_not_impl_any!(BodyStream>>: MessageBody); #[actix_rt::test] async fn skips_empty_chunks() { diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index cac6b7eb9..c22310c25 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -105,14 +105,14 @@ impl MessageBody for BoxBody { #[cfg(test)] mod tests { - use static_assertions::{assert_impl_all, assert_not_impl_all}; + use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; use crate::body::to_bytes; assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin); - assert_not_impl_all!(BoxBody: Send, Sync, Unpin); + assert_not_impl_any!(BoxBody: Send, Sync, Unpin); #[actix_rt::test] async fn nested_boxed_body() { diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index 9c1727246..e5e27b287 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -76,7 +76,7 @@ mod tests { use actix_rt::pin; use actix_utils::future::poll_fn; use futures_util::stream; - use static_assertions::{assert_impl_all, assert_not_impl_all}; + use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; use crate::body::to_bytes; @@ -87,10 +87,10 @@ mod tests { assert_impl_all!(SizedStream>>: MessageBody); assert_impl_all!(SizedStream>>: MessageBody); - assert_not_impl_all!(SizedStream>: MessageBody); - assert_not_impl_all!(SizedStream>: MessageBody); + assert_not_impl_any!(SizedStream>: MessageBody); + assert_not_impl_any!(SizedStream>: MessageBody); // crate::Error is not Clone - assert_not_impl_all!(SizedStream>>: MessageBody); + assert_not_impl_any!(SizedStream>>: MessageBody); #[actix_rt::test] async fn skips_empty_chunks() { diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs index d09a943ab..83007ae2d 100644 --- a/awc/src/any_body.rs +++ b/awc/src/any_body.rs @@ -160,7 +160,7 @@ impl fmt::Debug for AnyBody { mod tests { use std::marker::PhantomPinned; - use static_assertions::{assert_impl_all, assert_not_impl_all}; + use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; @@ -187,6 +187,6 @@ mod tests { assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin); assert_impl_all!(AnyBody: MessageBody); - assert_not_impl_all!(AnyBody: Send, Sync, Unpin); - assert_not_impl_all!(AnyBody: Send, Sync, Unpin); + assert_not_impl_any!(AnyBody: Send, Sync, Unpin); + assert_not_impl_any!(AnyBody: Send, Sync, Unpin); } From ff4b2d251f7527ca8b0221821691947ab7f14dae Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 14:32:57 +0000 Subject: [PATCH 362/861] fix impl assertions --- actix-http/src/body/boxed.rs | 5 ++--- awc/src/any_body.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index c22310c25..5fcc42f56 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -110,9 +110,8 @@ mod tests { use super::*; use crate::body::to_bytes; - assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin); - - assert_not_impl_any!(BoxBody: Send, Sync, Unpin); + assert_impl_all!(BoxBody: fmt::Debug, MessageBody, Unpin); + assert_not_impl_any!(BoxBody: Send, Sync); #[actix_rt::test] async fn nested_boxed_body() { diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs index 83007ae2d..d9c259d8f 100644 --- a/awc/src/any_body.rs +++ b/awc/src/any_body.rs @@ -181,12 +181,12 @@ mod tests { } } - assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody>: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Send, Sync, Unpin); - assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin); - assert_impl_all!(AnyBody: MessageBody); + assert_impl_all!(AnyBody<()>: Send, Sync, Unpin, fmt::Debug, MessageBody); + assert_impl_all!(AnyBody>: Send, Sync, Unpin, fmt::Debug, MessageBody); + assert_impl_all!(AnyBody: Send, Sync, Unpin, fmt::Debug, MessageBody); + assert_impl_all!(AnyBody: Unpin, fmt::Debug, MessageBody); + assert_impl_all!(AnyBody: Send, Sync, MessageBody); - assert_not_impl_any!(AnyBody: Send, Sync, Unpin); - assert_not_impl_any!(AnyBody: Send, Sync, Unpin); + assert_not_impl_any!(AnyBody: Send, Sync); + assert_not_impl_any!(AnyBody: Unpin); } From 092dbba5b96d2ab194f6fe6fd8ff509edd73a213 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 15:24:35 +0000 Subject: [PATCH 363/861] update migration guide --- actix-web/MIGRATION-4.0.md | 31 +++++++++++++++++++---- actix-web/src/middleware/authors-guide.md | 13 ++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 actix-web/src/middleware/authors-guide.md diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index f6c2f9bc6..2f8341e1b 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -23,8 +23,7 @@ The MSRV of Actix Web has been raised from 1.42 to 1.54. ## Server Settings -Until actix-web v4, actix-server used the total number of available logical cores as the default number of worker threads. The new default number of worker threads for actix-server is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654cea5e55b169f6fd05693b765799733b1b#diff-96893e8cb2125e6eefc96105a8462c4fd834943ef5129ffbead1a114133ebb78). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957). - +Until actix-web v4, actix-server used the total number of available logical cores as the default number of worker threads. The new default number of worker threads for actix-server is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654cea5e55b169f6fd05693b765799733b1b#diff-96893e8cb2125e6eefc96105a8462c4fd834943ef5129ffbead1a114133ebb78). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957). ## Module Structure @@ -136,10 +135,32 @@ TODO TODO -## HttpResponse no longer implements Future +## Returning `HttpResponse` synchronously. + +The implementation of `Future` for `HttpResponse` was removed because it was largely useless for all but the simplest handlers like `web::to(|| HttpResponse::Ok().finish())`. It also caused false positives on the `async_yields_async` clippy lint in reasonable scenarios. The compiler errors will looks something like: + +``` +web::to(|| HttpResponse::Ok().finish()) +^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]` +``` + +This form should be replaced with the a more explicit async fn: + +```diff +- web::to(|| HttpResponse::Ok().finish()) ++ web::to(|| async { HttpResponse::Ok().finish() }) +``` + +Or, for these extremely simple cases, utilise an `HttpResponseBuilder`: + +```diff +- web::to(|| HttpResponse::Ok().finish()) ++ web::to(HttpResponse::Ok) +``` -TODO ## `#[actix_web::main]` and `#[tokio::main]` -TODO +Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses of Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task. + +For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle. diff --git a/actix-web/src/middleware/authors-guide.md b/actix-web/src/middleware/authors-guide.md new file mode 100644 index 000000000..344523a1a --- /dev/null +++ b/actix-web/src/middleware/authors-guide.md @@ -0,0 +1,13 @@ +# Middleware Author's Guide + +## What Is A Middleware? + +## Middleware Traits + +## Understanding Body Types + +## Best Practices + +## Error Propagation + +## When To (Not) Use Middleware From e0f02c1d9e764140b93d5eeaa853a667293907b9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Feb 2022 16:53:09 +0000 Subject: [PATCH 364/861] update migration guide --- actix-web/MIGRATION-4.0.md | 132 +++++++++++++++--- actix-web/src/response/customize_responder.rs | 2 +- actix-web/src/response/responder.rs | 9 ++ 3 files changed, 122 insertions(+), 21 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 2f8341e1b..acbb3bc37 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -9,9 +9,9 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob ## Table of Contents: - [MSRV](#msrv) -- [Server Settings](#server-settings) - [Module Structure](#module-structure) - [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning) +- [Server Settings :warning:](#server-settings-warning) - [`FromRequest` Trait](#fromrequest-trait) - [Compression Feature Flags](#compression-feature-flags) - [`web::Path`](#webpath) @@ -21,10 +21,6 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob The MSRV of Actix Web has been raised from 1.42 to 1.54. -## Server Settings - -Until actix-web v4, actix-server used the total number of available logical cores as the default number of worker threads. The new default number of worker threads for actix-server is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654cea5e55b169f6fd05693b765799733b1b#diff-96893e8cb2125e6eefc96105a8462c4fd834943ef5129ffbead1a114133ebb78). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957). - ## Module Structure Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. @@ -45,6 +41,12 @@ The default `NormalizePath` behavior now strips trailing slashes by default. Thi Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. +## Server Settings :warning: + +Until Actix Web v4, the underlying `actix-server` crate used the number of available **logical** cores as the default number of worker threads. The new default is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654c). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957). + +If you notice performance regressions, please open a new issue detailing your observations. + ## `FromRequest` Trait The associated type `Config` of `FromRequest` was removed. If you have custom extractors, you can just remove this implementation and refer to config types directly, if required. @@ -59,14 +61,12 @@ Consequently, the `FromRequest::configure` method was also removed. Config for e ## Compression Feature Flags -Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are: +Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are: - `compress-brotli` - `compress-gzip` - `compress-zstd` -If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want to have compression enabled. - ## `web::Path` The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl. @@ -90,7 +90,7 @@ Actix Web's sister crate `awc` is no longer re-exported through the `client` mod + use awc::Client; ``` -## Integration Testing Utils Moved to `actix-test` +## Integration Testing Utils Moved To `actix-test` Actix Web's `test` module used to contain `TestServer`. Since this required the `awc` client and it was removed as a re-export (see above), it was moved to its own crate [`actix-test`](https://docs.rs/actix-test). @@ -101,7 +101,22 @@ Actix Web's `test` module used to contain `TestServer`. Since this required the ## Header APIs -TODO +Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. Most of the the old methods have only been deprecated with notes that will guide how to update. + +In short, "insert" always indicates that existing any existing headers with the same name are overridden and "append" indicates adding with no removal. + +For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive. + +```diff +- .set_header("Api-Key", "1234") ++ .insert_header(("Api-Key", "1234")) + +- .header("Api-Key", "1234") ++ .append_header(("Api-Key", "1234")) + +- .set(ContentType::json()) ++ .insert_header(ContentType::json()) +``` ## Body Types / Removal of Body+ResponseBody types / Addition of EitherBody @@ -117,25 +132,103 @@ TODO: Also write the Middleware author's guide. ## `Responder` Trait -TODO +The `Responder` trait's interface has changed. Errors should be handled and converted to responses within the `respond_to` method. It's also no longer async so the associated `type Future` has been removed; there was no compelling use case found for it. These changes simplify the interface and implementation a lot. -## `App::data` deprecation +Now that more emphasis is placed on expressive body types, as explained in the [body types migration section](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody), this trait has introduced an associated `type Body`. The simplest migration will be to use `BoxBody` + `.map_into_boxed_body()` but if there is a more expressive type for your responder then try to use that instead. -TODO +```diff + impl Responder for &'static str { +- type Error = Error; +- type Future = Ready>; ++ type Body = &'static str; -## It's probably not necessary to import `actix-rt` or `actix-service` any more +- fn respond_to(self, req: &HttpRequest) -> Self::Future { ++ fn respond_to(self, req: &HttpRequest) -> HttpResponse { + let res = HttpResponse::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self); -TODO +- ok(res) ++ res + } + } +``` -## Server must be awaited in order to run :warning: +## `App::data` deprecation :warning: -TODO +The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods was a footgun and lead to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments. + +You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly. + +```diff + use actix_web::web::Data; + + #[get("/")] + async fn handler(my_state: Data) -> { todo!() } + + HttpServer::new(|| { +- App::new() +- .data(MyState::default()) +- .service(hander) + ++ let my_state: Data = Data::new(MyState::default()); ++ ++ App::new() ++ .app_data(my_state) ++ .service(hander) + }) +``` + +## Direct Dependency On `actix-rt` And `actix-service` + +Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular, all traits necessary for creating middleware are re-exported through the `dev` modules and `#[actix_web::test]` now exists for async test definitions. Relying on the these re-exports will ease transition to future versions of Actix Web. + +```diff +- use actix_service::{Service, Transform}; ++ use actix_web::dev::{Service, Transform}; +``` + +```diff +- #[actix_rt::test] ++ #[actix_web::test] + async fn test_thing() { +``` + +## Server Must Be Polled :warning: + +In order to _start_ serving requests, the `Server` object returned from `run` **must** be `poll`ed, `await`ed, or `spawn`ed. This was done to prevent unexpected behavior and ensure that things like signal handlers are able to function correctly when enabled. + +For example, in this contrived example where the server is started and then the main thread is sent to sleep, the server will no longer be able to serve requests with v4.0: + +```rust +#[actix_web::main] +async fn main() { + HttpServer::new(|| App::new().default_service(web::to(HttpResponse::Conflict))) + .bind(("127.0.0.1", 8080)) + .unwrap() + .run(); + + thread::sleep(Duration::from_secs(1000)); +} +``` ## Guards API -TODO +Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API provided is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details. -## Returning `HttpResponse` synchronously. +```diff + struct MethodGuard(HttpMethod); + + impl Guard for MethodGuard { +- fn check(&self, request: &RequestHead) -> bool { ++ fn check(&self, ctx: &GuardContext<'_>) -> bool { +- request.method == self.0 ++ ctx.head().method == self.0 + } + } +``` + +## Returning `HttpResponse` synchronously The implementation of `Future` for `HttpResponse` was removed because it was largely useless for all but the simplest handlers like `web::to(|| HttpResponse::Ok().finish())`. It also caused false positives on the `async_yields_async` clippy lint in reasonable scenarios. The compiler errors will looks something like: @@ -158,7 +251,6 @@ Or, for these extremely simple cases, utilise an `HttpResponseBuilder`: + web::to(HttpResponse::Ok) ``` - ## `#[actix_web::main]` and `#[tokio::main]` Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses of Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task. diff --git a/actix-web/src/response/customize_responder.rs b/actix-web/src/response/customize_responder.rs index 8cb146dda..f6f4b9236 100644 --- a/actix-web/src/response/customize_responder.rs +++ b/actix-web/src/response/customize_responder.rs @@ -7,7 +7,7 @@ use crate::{HttpRequest, HttpResponse, Responder}; /// Allows overriding status code and headers for a [`Responder`]. /// -/// Created by the [`Responder::customize`] method. +/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type. pub struct CustomizeResponder { inner: CustomizeResponderInner, error: Option, diff --git a/actix-web/src/response/responder.rs b/actix-web/src/response/responder.rs index cb71369cf..c88faec89 100644 --- a/actix-web/src/response/responder.rs +++ b/actix-web/src/response/responder.rs @@ -47,6 +47,15 @@ pub trait Responder { CustomizeResponder::new(self) } + #[doc(hidden)] + #[deprecated(since = "4.0.0", note = "Prefer `.customize().with_status(header)`.")] + fn with_status(self, status: StatusCode) -> CustomizeResponder + where + Self: Sized, + { + self.customize().with_status(status) + } + #[doc(hidden)] #[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header(header)`.")] fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder From a9f445875a79b23d44beaeb0a6adb4f3d0092fd3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Feb 2022 12:31:06 +0000 Subject: [PATCH 365/861] update migration guide --- actix-web/MIGRATION-4.0.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index acbb3bc37..e71387c94 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -15,7 +15,20 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob - [`FromRequest` Trait](#fromrequest-trait) - [Compression Feature Flags](#compression-feature-flags) - [`web::Path`](#webpath) -- [Rustls](#rustls-crate-upgrade) +- [Rustls Crate Upgrade](#rustls-crate-upgrade) +- [Removed `awc` Client Re-export](#removed-awc-client-re-export) +- [Integration Testing Utils Moved To `actix-test`](#integration-testing-utils-moved-to-actix-test) +- [Header APIs](#header-apis) +- [Body Types / Removal of Body+ResponseBody types / Addition of EitherBody](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody) +- [Middleware Trait APIs](#middleware-trait-apis) +- [`Responder` Trait](#responder-trait) +- [`App::data` Deprecation :warning:](#appdata-deprecation-warning) +- [Direct Dependency On `actix-rt` And `actix-service`](#direct-dependency-on-actix-rt-and-actix-service) +- [Server Must Be Polled :warning:](#server-must-be-polled-warning) +- [Guards API](#guards-api) +- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) +- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain) +- [`web::block`](#webblock) ## MSRV @@ -126,6 +139,8 @@ In particular, folks seem to be struggling with the `ErrorHandlers` middleware b ## Middleware Trait APIs +> This section builds upon guidance from the [response body types](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody) section. + TODO TODO: Also write the Middleware author's guide. @@ -154,7 +169,7 @@ Now that more emphasis is placed on expressive body types, as explained in the [ } ``` -## `App::data` deprecation :warning: +## `App::data` Deprecation :warning: The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods was a footgun and lead to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments. @@ -218,7 +233,7 @@ Implementors of routing guards will need to use the modified interface of the `G ```diff struct MethodGuard(HttpMethod); - + impl Guard for MethodGuard { - fn check(&self, request: &RequestHead) -> bool { + fn check(&self, ctx: &GuardContext<'_>) -> bool { @@ -256,3 +271,15 @@ Or, for these extremely simple cases, utilise an `HttpResponseBuilder`: Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses of Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task. For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle. + +## `web::block` + +The `web::block` helper has changed return type from roughly `async fn(fn() -> Result) Result>` to `async fn(fn() -> T) Result`. That's to say that the blocking function can now return things that are not `Result`s and it does not wrap error types anymore. If you still need to return `Result`s then you'll likely want to use double `?` after the `.await`. + +```diff +- let n: u32 = web::block(|| Ok(123)).await?; ++ let n: u32 = web::block(|| 123).await?; + +- let n: u32 = web::block(|| Ok(123)).await?; ++ let n: u32 = web::block(|| Ok(123)).await??; +``` From 1b706b3069d47e7b52366aeb91e10d922aa68bc0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Feb 2022 16:12:39 +0000 Subject: [PATCH 366/861] update body type migration guide --- actix-http/src/body/either.rs | 16 ++++++++++++- actix-http/src/body/message_body.rs | 2 +- actix-web/MIGRATION-4.0.md | 37 ++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index add1eab7c..92bd89984 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -10,6 +10,17 @@ use super::{BodySize, BoxBody, MessageBody}; use crate::Error; pin_project! { + /// An "either" type specialized for body types. + /// + /// It is common, in middleware especially, to conditionally return an inner service's unknown/ + /// generic body `B` type or return early with a new response. This type's "right" variant + /// defaults to `BoxBody` since error responses are the common case. + /// + /// For example, middleware will often have `type Response = ServiceResponse>`. + /// This means that the inner service's response body type maps to the `Left` variant and the + /// middleware's own error responses use the default `Right` variant of `BoxBody`. Of course, + /// there's no reason it couldn't use `EitherBody` instead if its alternative + /// responses have a known type. #[project = EitherBodyProj] #[derive(Debug, Clone)] pub enum EitherBody { @@ -22,7 +33,10 @@ pin_project! { } impl EitherBody { - /// Creates new `EitherBody` using left variant and boxed right variant. + /// Creates new `EitherBody` left variant with a boxed right variant. + /// + /// If the expected `R` type will be inferred and is not `BoxBody` then use the + /// [`left`](Self::left) constructor instead. #[inline] pub fn new(body: L) -> Self { Self::Left { body } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 86ff09fbe..9090e34d5 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -19,7 +19,7 @@ use super::{BodySize, BoxBody}; /// It is not usually necessary to create custom body types, this trait is already [implemented for /// a large number of sensible body types](#foreign-impls) including: /// - Empty body: `()` -/// - Text-based: `String`, `&'static str`, `ByteString`. +/// - Text-based: `String`, `&'static str`, [`ByteString`](https://docs.rs/bytestring/1). /// - Byte-based: `Bytes`, `BytesMut`, `Vec`, `&'static [u8]`; /// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream) /// diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e71387c94..d478456fa 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -19,7 +19,7 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob - [Removed `awc` Client Re-export](#removed-awc-client-re-export) - [Integration Testing Utils Moved To `actix-test`](#integration-testing-utils-moved-to-actix-test) - [Header APIs](#header-apis) -- [Body Types / Removal of Body+ResponseBody types / Addition of EitherBody](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody) +- [Response Body Types](#response-body-types) - [Middleware Trait APIs](#middleware-trait-apis) - [`Responder` Trait](#responder-trait) - [`App::data` Deprecation :warning:](#appdata-deprecation-warning) @@ -131,15 +131,40 @@ For request and response builder APIs, the new methods provide a unified interfa + .insert_header(ContentType::json()) ``` -## Body Types / Removal of Body+ResponseBody types / Addition of EitherBody +## Response Body Types -TODO +There have been a lot of changes to response body types. The general theme is that they are now more expressive and their purposes are more obvious. -In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types. +All items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body) have much better documentation now. + +### `ResponseBody` + +`ResponseBody` is gone. Its purpose was confusing and has been replaced by better components. + +### `Body` + +`Body` is also gone. In combination with `ResponseBody`, the API it provided was sub-optimal and did not encourage expressive types. Here are the equivalents in the new system (check docs): + +- `Body::None` => `body::None::new()` +- `Body::Empty` => `()` / `web::Bytes::new()` +- `Body::Bytes` => `web::Bytes::from(...)` +- `Body::Message` => `.boxed()` / `BoxBody` + +### `BoxBody` + +`BoxBody` is a new type erased body type. It's used for all error response bodies use this. Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type. + +### `EitherBody` + +`EitherBody` is a new "either" type that is particularly useful in middleware that can bail early, returning their own response plus body type. + +### Error Handlers + +TODO In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types. ## Middleware Trait APIs -> This section builds upon guidance from the [response body types](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody) section. +This section builds upon guidance from the [response body types](#response-body-types) section. TODO @@ -149,7 +174,7 @@ TODO: Also write the Middleware author's guide. The `Responder` trait's interface has changed. Errors should be handled and converted to responses within the `respond_to` method. It's also no longer async so the associated `type Future` has been removed; there was no compelling use case found for it. These changes simplify the interface and implementation a lot. -Now that more emphasis is placed on expressive body types, as explained in the [body types migration section](#body-types--removal-of-bodyresponsebody-types--addition-of-eitherbody), this trait has introduced an associated `type Body`. The simplest migration will be to use `BoxBody` + `.map_into_boxed_body()` but if there is a more expressive type for your responder then try to use that instead. +Now that more emphasis is placed on expressive body types, as explained in the [body types migration section](#response-body-types), this trait has introduced an associated `type Body`. The simplest migration will be to use `BoxBody` + `.map_into_boxed_body()` but if there is a more expressive type for your responder then try to use that instead. ```diff impl Responder for &'static str { From 4c59a34513ebd05e0aa0bd09e167dfc02b81b6d0 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 10 Feb 2022 05:29:00 -0500 Subject: [PATCH 367/861] Remove clone implementation for `Path` (#2639) --- actix-web/src/types/path.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actix-web/src/types/path.rs b/actix-web/src/types/path.rs index 869269d09..0fcac2c19 100644 --- a/actix-web/src/types/path.rs +++ b/actix-web/src/types/path.rs @@ -53,9 +53,7 @@ use crate::{ /// format!("Welcome {}!", info.name) /// } /// ``` -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From, -)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From)] pub struct Path(T); impl Path { From 3486edabcfb646eb31b2a2ae8db8e703a53318a6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 15 Feb 2022 00:54:12 +0000 Subject: [PATCH 368/861] update migrations guide re tokio v1 --- actix-web/MIGRATION-4.0.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index d478456fa..01aa642bd 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -2,13 +2,14 @@ It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder. -This is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, are shown in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or PR. +This is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or PR. -Headings marked with :warning: are **breaking behavioral changes** and will probably not surface as compile-time errors. Automated tests _might_ detect their effects on your app. +Headings marked with :warning: are **breaking behavioral changes** that will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. ## Table of Contents: - [MSRV](#msrv) +- [Tokio v1 Ecosystem](#tokio-v1-ecosystem) - [Module Structure](#module-structure) - [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning) - [Server Settings :warning:](#server-settings-warning) @@ -34,6 +35,17 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob The MSRV of Actix Web has been raised from 1.42 to 1.54. +## Tokio v1 Ecosystem + +Actix Web v4 is now underpinned by the the Tokio v1 ecosystem of crates. If you have dependencies that might utilize Tokio directly, it is worth checking to see if an update is available. The following command will assist in finding such dependencies: + +```sh +cargo tree -i tokio + +# if multiple tokio versions are depended on, show the older ones with: +cargo tree -i tokio:0.2.25 +``` + ## Module Structure Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. From de62e8b025a6f16ee54f7c5e73c85f44619071f6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 15 Feb 2022 14:40:26 +0000 Subject: [PATCH 369/861] add nextest to post-merge ci --- .github/workflows/ci-post-merge.yml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 4ae925452..d37b2c107 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -152,3 +152,34 @@ jobs: # - name: Upload to Codecov # uses: codecov/codecov-action@v1 # with: { file: cobertura.xml } + + nextest: + name: nextest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.3.0 + + - name: Install cargo-nextest + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-nextest + + - name: Test with cargo-nextest + uses: actions-rs/cargo@v1 + with: + command: nextest + args: run From a808a26d8c55073866a3fb9fe339bf4456a5b82f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 15 Feb 2022 20:49:10 +0000 Subject: [PATCH 370/861] bump actix-codec to 0.5 --- actix-http-test/Cargo.toml | 4 ++-- actix-http/Cargo.toml | 8 ++++---- actix-http/src/lib.rs | 2 ++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 6 +++--- docs/graphs/web-focus.dot | 2 +- 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e9986ef81..94e332177 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -30,8 +30,8 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0" -actix-codec = "0.4.1" -actix-tls = "3.0.0" +actix-codec = "0.5" +actix-tls = "3" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ba8f5fa1d..88eb6c3d2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -20,7 +20,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] +features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] [lib] name = "actix_http" @@ -57,7 +57,7 @@ __compress = [] [dependencies] actix-service = "2" -actix-codec = "0.4.1" +actix-codec = "0.5" actix-utils = "3" actix-rt = { version = "2.2", default-features = false } @@ -89,7 +89,7 @@ rand = { version = "0.8", optional = true } sha-1 = { version = "0.10", optional = true } # openssl/rustls -actix-tls = { version = "3.0.0", default-features = false, optional = true } +actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } @@ -99,7 +99,7 @@ zstd = { version = "0.10", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" -actix-tls = { version = "3.0.0", features = ["openssl"] } +actix-tls = { version = "3", features = ["openssl"] } actix-web = "4.0.0-rc.3" async-stream = "0.3" diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index dbff89612..360cb86fc 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -3,6 +3,7 @@ //! ## Crate Features //! | Feature | Functionality | //! | ------------------- | ------------------------------------------- | +//! | `http2` | HTTP/2 support via [h2]. | //! | `openssl` | TLS support via [OpenSSL]. | //! | `rustls` | TLS support via [rustls]. | //! | `compress-brotli` | Payload compression support: Brotli. | @@ -10,6 +11,7 @@ //! | `compress-zstd` | Payload compression support: Zstd. | //! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | //! +//! [h2]: https://crates.io/crates/h2 //! [OpenSSL]: https://crates.io/crates/openssl //! [rustls]: https://crates.io/crates/rustls //! [trust-dns]: https://crates.io/crates/trust-dns diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 0f8aff074..26923258c 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -28,7 +28,7 @@ rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"] openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] -actix-codec = "0.4.1" +actix-codec = "0.5" actix-http = "3.0.0-rc.2" actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0499e19e4..0f4bca534 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } -actix-codec = "0.4.1" +actix-codec = "0.5" actix-http = "3.0.0-rc.2" actix-web = { version = "4.0.0-rc.3", default-features = false } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 17b2f2356..f9ea36737 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -63,7 +63,7 @@ __compress = [] experimental-io-uring = ["actix-server/io-uring"] [dependencies] -actix-codec = "0.4.1" +actix-codec = "0.5" actix-macros = { version = "0.2.3", optional = true } actix-rt = { version = "2.6", default-features = false } actix-server = "2" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e9cc5d656..57a2b8c8b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -58,11 +58,11 @@ __compress = [] dangerous-h2c = [] [dependencies] -actix-codec = "0.4.1" +actix-codec = "0.5" actix-service = "2.0.0" actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.0.0", features = ["connect", "uri"] } +actix-tls = { version = "3", features = ["connect", "uri"] } actix-utils = "3.0.0" ahash = "0.7" @@ -97,7 +97,7 @@ actix-http = { version = "3.0.0-rc.2", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } -actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } +actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-rc.3", features = ["openssl"] } diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 16b2d415e..a8c800b48 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -34,7 +34,7 @@ digraph { "utils" -> { "service" "rt" "codec" } "tracing" -> { "service" } "tls" -> { "service" "codec" "utils" } - "server" -> { "service" "rt" "codec" "utils" } + "server" -> { "service" "rt" "utils" } "rt" -> { "macros" } { rank=same; "utils" "codec" }; From 594e3a6ef134d125bf8781eb88d33b41e6b38c60 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Feb 2022 03:07:12 +0000 Subject: [PATCH 371/861] prepare actix-http release 3.0.0-rc.3 --- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 4 ++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 15 insertions(+), 11 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 08bd2f359..2b0af10e7 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-rc.2" +actix-http = "3.0.0-rc.3" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-rc.3", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 94e332177..591acdc45 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-rc.2" +actix-http = "3.0.0-rc.3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e9191b0fe..c8581c0b5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-rc.3 - 2022-02-16 +- No significant changes since `3.0.0-rc.2`. + + ## 3.0.0-rc.2 - 2022-02-08 ### Added - Implement `From>` for `Response>`. [#2625] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 88eb6c3d2..6f2e2dbcf 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index d06db8422..c1aae63e1 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.2)](https://docs.rs/actix-http/3.0.0-rc.2) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.3)](https://docs.rs/actix-http/3.0.0-rc.3) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.2) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.3) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 414c28862..91cc96904 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-rc.2" +actix-http = "3.0.0-rc.3" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 26923258c..3ba41b135 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" -actix-http = "3.0.0-rc.2" +actix-http = "3.0.0-rc.3" actix-http-test = "3.0.0-beta.12" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0f4bca534..dd6791d60 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.5" -actix-http = "3.0.0-rc.2" +actix-http = "3.0.0-rc.3" actix-web = { version = "4.0.0-rc.3", default-features = false } bytes = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index f9ea36737..8f952a917 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,7 +71,7 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" actix-web-codegen = { version = "0.5.0-rc.2", optional = true } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 57a2b8c8b..3d3b3c921 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" actix-service = "2.0.0" -actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-rc.2", features = ["openssl"] } +actix-http = { version = "3.0.0-rc.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } From a0c4bf8d1be8ab10efe7a7ba12456ff32d4ab0c9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Feb 2022 03:10:01 +0000 Subject: [PATCH 372/861] prepare awc release 3.0.0-beta.21 --- actix-http-test/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/CHANGES.md | 4 ++++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 591acdc45..0daefcd38 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -35,7 +35,7 @@ actix-tls = "3" actix-utils = "3.0.0" actix-rt = "2.2" actix-server = "2" -awc = { version = "3.0.0-beta.20", default-features = false } +awc = { version = "3.0.0-beta.21", default-features = false } base64 = "0.13" bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 3ba41b135..5fb51282e 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -35,7 +35,7 @@ actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] } +awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index dd6791d60..5634ec201 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.12" -awc = { version = "3.0.0-beta.20", default-features = false } +awc = { version = "3.0.0-beta.21", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 8f952a917..d0d78431a 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -101,7 +101,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.16" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.20", features = ["openssl"] } +awc = { version = "3.0.0-beta.21", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 05e524fad..3fd59512a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.21 - 2022-02-16 +- No significant changes since `3.0.0-beta.20`. + + ## 3.0.0-beta.20 - 2022-01-31 - No significant changes since `3.0.0-beta.19`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 3d3b3c921..960ff0689 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.20" +version = "3.0.0-beta.21" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index 2546ceeec..4e97b1789 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.20)](https://docs.rs/awc/3.0.0-beta.20) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.20/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.20) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From f5895d5effbc04d86c0054022ef2d93006a83042 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Feb 2022 03:11:22 +0000 Subject: [PATCH 373/861] prepare actix-web-actors release 4.0.0-beta.12 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index a8ff2701d..124fe23b1 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.12 - 2022-02-16 +- No significant changes since `4.0.0-beta.11`. + + ## 4.0.0-beta.11 - 2022-01-31 - No significant changes since `4.0.0-beta.10`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 5634ec201..de90d3cb0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.11" +version = "4.0.0-beta.12" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 4a491c29a..0964cb04e 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.11)](https://docs.rs/actix-web-actors/4.0.0-beta.11) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web-actors/4.0.0-beta.12) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 38e015432bd2b37509545b09adb5c040bd4f0595 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Feb 2022 03:13:22 +0000 Subject: [PATCH 374/861] prepare actix-http-test release 3.0.0-beta.13 --- actix-http-test/CHANGES.md | 4 ++++ actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index a909b1d6a..3b98e0972 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.13 - 2022-02-16 +- No significant changes since `3.0.0-beta.12`. + + ## 3.0.0-beta.12 - 2022-01-31 - No significant changes since `3.0.0-beta.11`. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0daefcd38..2fb4a4f77 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.12" +version = "3.0.0-beta.13" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index bf9dddfa2..d11ae69b2 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http-test/3.0.0-beta.12) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.12) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6f2e2dbcf..30ac4ce3a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.10", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } actix-web = "4.0.0-rc.3" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 5fb51282e..92ecf86be 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" actix-http = "3.0.0-rc.3" -actix-http-test = "3.0.0-beta.12" +actix-http-test = "3.0.0-beta.13" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 960ff0689..31f6b9840 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-http = { version = "3.0.0-rc.3", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } From 51e573b8882f44784a4eedad964ffbe046ef80cf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Feb 2022 03:13:41 +0000 Subject: [PATCH 375/861] prepare actix-test release 0.1.0-beta.13 --- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 4 ++++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 2b0af10e7..a006df953 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -43,6 +43,6 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.12" +actix-test = "0.1.0-beta.13" actix-web = "4.0.0-rc.3" tempfile = "3.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 0c8fc996b..13e75c01a 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.1.0-beta.13 - 2022-02-16 +- No significant changes since `0.1.0-beta.12`. + + ## 0.1.0-beta.12 - 2022-01-31 - Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611] diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 92ecf86be..21aeec1da 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.12" +version = "0.1.0-beta.13" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index de90d3cb0..121c86eb7 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.12" +actix-test = "0.1.0-beta.13" awc = { version = "3.0.0-beta.21", default-features = false } env_logger = "0.9" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index d5492243e..e3ff61509 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ syn = { version = "1", features = ["full", "parsing"] } [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.12" +actix-test = "0.1.0-beta.13" actix-utils = "3.0.0" actix-web = "4.0.0-rc.3" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index d0d78431a..938412090 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -100,7 +100,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6.0-beta.16" -actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.21", features = ["openssl"] } brotli = "3.3.3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 31f6b9840..7076897b1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } actix-http = { version = "3.0.0-rc.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" -actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] } +actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-utils = "3.0.0" actix-web = { version = "4.0.0-rc.3", features = ["openssl"] } From 52f7d96358e6c2e6f02953e1096cfd6fa0a3f542 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 17 Feb 2022 19:13:03 +0000 Subject: [PATCH 376/861] tweak migration document --- actix-web/MIGRATION-4.0.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 01aa642bd..b013f12b2 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -48,7 +48,7 @@ cargo tree -i tokio:0.2.25 ## Module Structure -Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations. +Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations. ## `NormalizePath` Middleware :warning: @@ -289,7 +289,14 @@ web::to(|| HttpResponse::Ok().finish()) ^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]` ``` -This form should be replaced with the a more explicit async fn: +This form should be replaced with explicit async functions and closures: + +```diff +- fn handler() -> HttpResponse { ++ async fn handler() -> HttpResponse { + HttpResponse::Ok().finish() + } +``` ```diff - web::to(|| HttpResponse::Ok().finish()) From f843776f361bce3fd7e0f653e6add738faddc793 Mon Sep 17 00:00:00 2001 From: Xavier Lange Date: Thu, 17 Feb 2022 22:34:12 -0500 Subject: [PATCH 377/861] Fix links in README (#2653) --- actix-web/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/actix-web/README.md b/actix-web/README.md index 4adeb3910..188c0df28 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -77,14 +77,18 @@ async fn main() -> std::io::Result<()> { - [Application State](https://github.com/actix/examples/tree/master/basics/state/) - [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) - [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) -- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/) -- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/) -- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/) -- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/) -- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/) -- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/) -- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/) +- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel/) +- [MongoDB Integration](https://github.com/actix/examples/tree/master/databases/mongodb/) +- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres/) +- [Rbatis Integration](https://github.com/actix/examples/tree/master/databases/rbatis/) +- [Redis Integration](https://github.com/actix/examples/tree/master/databases/redis/) +- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite/) +- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/) - [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) +- [Tera Templates](https://github.com/actix/examples/tree/master/templating/tera/) +- [Askama Templates](https://github.com/actix/examples/tree/master/templating/askama/) +- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/https-tls/rustls/) +- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/https-tls/openssl/) You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. From b291e298822ed60234f060e3ec41fee2526b66c2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 18 Feb 2022 03:41:10 +0000 Subject: [PATCH 378/861] fix links --- actix-files/README.md | 4 ++-- actix-web/MIGRATION-4.0.md | 2 +- actix-web/README.md | 37 +++++++++++++++----------------- actix-web/examples/on-connect.rs | 2 +- awc/README.md | 2 +- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/actix-files/README.md b/actix-files/README.md index 669efc0ab..8ac80860e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -13,6 +13,6 @@ ## Documentation & Resources -- [API Documentation](https://docs.rs/actix-files/) -- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) +- [API Documentation](https://docs.rs/actix-files) +- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files) - Minimum Supported Rust Version (MSRV): 1.54 diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index b013f12b2..e33aee4a3 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -104,7 +104,7 @@ The inner field for `web::Path` was made private because It was causing too many ## Rustls Crate Upgrade -Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/) +Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/master/https-tls/rustls/) ## Removed `awc` Client Re-export diff --git a/actix-web/README.md b/actix-web/README.md index 188c0df28..e66224524 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -71,26 +71,24 @@ async fn main() -> std::io::Result<()> { } ``` -### More examples +### More Examples -- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/) -- [Application State](https://github.com/actix/examples/tree/master/basics/state/) -- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/) -- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/) -- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel/) -- [MongoDB Integration](https://github.com/actix/examples/tree/master/databases/mongodb/) -- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres/) -- [Rbatis Integration](https://github.com/actix/examples/tree/master/databases/rbatis/) -- [Redis Integration](https://github.com/actix/examples/tree/master/databases/redis/) -- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite/) -- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/) -- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/) -- [Tera Templates](https://github.com/actix/examples/tree/master/templating/tera/) -- [Askama Templates](https://github.com/actix/examples/tree/master/templating/askama/) -- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/https-tls/rustls/) -- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/https-tls/openssl/) +- [Hello World](https://github.com/actix/examples/tree/master/basics/hello-world) +- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics) +- [Application State](https://github.com/actix/examples/tree/master/basics/state) +- [JSON Handling](https://github.com/actix/examples/tree/master/json/json) +- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart) +- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel) +- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite) +- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres) +- [Tera Templates](https://github.com/actix/examples/tree/master/templating/tera) +- [Askama Templates](https://github.com/actix/examples/tree/master/templating/askama) +- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/https-tls/rustls) +- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/https-tls/openssl) +- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets) +- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat) -You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples. +You may consider checking out [this directory](https://github.com/actix/examples/tree/master) for more examples. ## Benchmarks @@ -105,5 +103,4 @@ This project is licensed under either of the following licenses, at your option: ## Code of Conduct -Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. -The Actix team promises to intervene to uphold that code of conduct. +Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. The Actix team promises to intervene to uphold that code of conduct. diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs index d76e9ce56..24c6f8418 100644 --- a/actix-web/examples/on-connect.rs +++ b/actix-web/examples/on-connect.rs @@ -2,7 +2,7 @@ //! properties and pass them to a handler through request-local data. //! //! For an example of extracting a client TLS certificate, see: -//! +//! use std::{any::Any, io, net::SocketAddr}; diff --git a/awc/README.md b/awc/README.md index 4e97b1789..417647e62 100644 --- a/awc/README.md +++ b/awc/README.md @@ -11,7 +11,7 @@ ## Documentation & Resources - [API Documentation](https://docs.rs/awc) -- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) +- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https) - Minimum Supported Rust Version (MSRV): 1.54 ## Example From f94065398138a74c317373e59ab9e0937322b89c Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Sat, 19 Feb 2022 17:05:54 +0000 Subject: [PATCH 379/861] Edits to the migration notes (#2654) --- actix-web/MIGRATION-4.0.md | 64 +++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e33aee4a3..b5109e3f2 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -1,10 +1,12 @@ # Migrating to 4.0.0 -It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder. +This guide walks you through the process of migrating from v3.x.y to v4.x.y. +If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder. -This is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or PR. +This document is not designed to be exhaustive - it focuses on the most significant changes coming in v4. +You can find an exhaustive changelog in [CHANGES.md](./CHANGES.md), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. -Headings marked with :warning: are **breaking behavioral changes** that will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. +Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. ## Table of Contents: @@ -37,22 +39,29 @@ The MSRV of Actix Web has been raised from 1.42 to 1.54. ## Tokio v1 Ecosystem -Actix Web v4 is now underpinned by the the Tokio v1 ecosystem of crates. If you have dependencies that might utilize Tokio directly, it is worth checking to see if an update is available. The following command will assist in finding such dependencies: +Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem. +`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly - if they are using an older version of `tokio`, check if an update is available. +The following command can help you to identify these dependencies: ```sh +# Find all crates in your dependency tree that depend on `tokio` +# It also reports the different versions of `tokio` in your dependency tree. cargo tree -i tokio -# if multiple tokio versions are depended on, show the older ones with: +# if you depend on multiple versions of tokio, use this command to +# list the dependencies relying on a specific version of tokio: cargo tree -i tokio:0.2.25 ``` ## Module Structure -Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations. +Lots of modules have been re-organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", check the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations. ## `NormalizePath` Middleware :warning: -The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead. +The default `NormalizePath` behavior now strips trailing slashes by default. +This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed - the discrepancy has now been fixed. +As a consequence of this change, routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. Calling `NormalizePath::default()` will log a warning. We suggest to use `new` or `trim`. ```diff - #[get("/test/")] @@ -86,7 +95,7 @@ Consequently, the `FromRequest::configure` method was also removed. Config for e ## Compression Feature Flags -Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are: +The `compress` feature flag has been split into more granular feature flags, one for each supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are: - `compress-brotli` - `compress-gzip` @@ -94,7 +103,8 @@ Feature flag `compress` has been split into its supported algorithm (brotli, gzi ## `web::Path` -The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl. +The inner field for `web::Path` is now private. +It was causing too many issues when used with inner tuple types due to its `Deref` implementation. ```diff - async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) { @@ -104,11 +114,11 @@ The inner field for `web::Path` was made private because It was causing too many ## Rustls Crate Upgrade -Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/master/https-tls/rustls/) +Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/) ## Removed `awc` Client Re-export -Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` its own release cadence and prevents its own breaking changes from being blocked due to a re-export. +Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence - its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule. ```diff - use actix_web::client::Client; @@ -117,18 +127,20 @@ Actix Web's sister crate `awc` is no longer re-exported through the `client` mod ## Integration Testing Utils Moved To `actix-test` -Actix Web's `test` module used to contain `TestServer`. Since this required the `awc` client and it was removed as a re-export (see above), it was moved to its own crate [`actix-test`](https://docs.rs/actix-test). +`TestServer` has been moved to its own crate, [`actix-test`](https://docs.rs/actix-test). ```diff - use use actix_web::test::start; + use use actix_test::start; ``` +`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above). + ## Header APIs -Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. Most of the the old methods have only been deprecated with notes that will guide how to update. +Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. -In short, "insert" always indicates that existing any existing headers with the same name are overridden and "append" indicates adding with no removal. +In short, "insert" always indicates that any existing headers with the same name are overridden, while "append" is used for adding with no removal (e.g. multi-valued headers). For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive. @@ -143,11 +155,13 @@ For request and response builder APIs, the new methods provide a unified interfa + .insert_header(ContentType::json()) ``` +We chose to deprecate most of the old methods instead of removing them immediately - the warning notes will guide you on how to update. + ## Response Body Types -There have been a lot of changes to response body types. The general theme is that they are now more expressive and their purposes are more obvious. +There have been a lot of changes to response body types. They are now more expressive and their purpose should be more intuitive. -All items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body) have much better documentation now. +We have boosted the quality and completeness of the documentation for all items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body). ### `ResponseBody` @@ -164,11 +178,12 @@ All items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body) hav ### `BoxBody` -`BoxBody` is a new type erased body type. It's used for all error response bodies use this. Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type. +`BoxBody` is a new type-erased body type. It's used for all error response bodies. +Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type. ### `EitherBody` -`EitherBody` is a new "either" type that is particularly useful in middleware that can bail early, returning their own response plus body type. +`EitherBody` is a new "either" type that is particularly useful in middlewares that can bail early, returning their own response plus body type. ### Error Handlers @@ -208,7 +223,7 @@ Now that more emphasis is placed on expressive body types, as explained in the [ ## `App::data` Deprecation :warning: -The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods was a footgun and lead to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments. +The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods led to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments. You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly. @@ -233,7 +248,12 @@ You may need to review the [guidance on shared mutable state](https://docs.rs/ac ## Direct Dependency On `actix-rt` And `actix-service` -Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular, all traits necessary for creating middleware are re-exported through the `dev` modules and `#[actix_web::test]` now exists for async test definitions. Relying on the these re-exports will ease transition to future versions of Actix Web. +Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular: + +- all traits necessary for creating middlewares are now re-exported through the `dev` modules; +- `#[actix_web::test]` now exists for async test definitions. + +Relying on these re-exports will ease the transition to future versions of Actix Web. ```diff - use actix_service::{Service, Transform}; @@ -266,7 +286,7 @@ async fn main() { ## Guards API -Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API provided is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details. +Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details. ```diff struct MethodGuard(HttpMethod); @@ -312,7 +332,7 @@ Or, for these extremely simple cases, utilise an `HttpResponseBuilder`: ## `#[actix_web::main]` and `#[tokio::main]` -Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses of Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task. +Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task. For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle. From 1ce58ecb305c60e51db06e6c913b7a1344e229ca Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 00:19:48 +0000 Subject: [PATCH 380/861] fix dispatcher panic on pending flush fixes thread panic in actix-http-3.0.0-rc.3 #2655 --- actix-http/src/h1/dispatcher.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index fbc7e5b99..f029fb108 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -931,10 +931,16 @@ where "dispatcher should not be in keep-alive phase if state is not none: {:?}", this.state, ); - debug_assert!( - this.write_buf.is_empty(), - "dispatcher should not be in keep-alive phase if write_buf is not empty", - ); + + // Assert removed by @robjtede on account of issue #2655. There are cases where an I/O + // flush can be pending after entering the keep-alive state causing the subsequent flush + // wake up to panic here. This appears to be a Linux-only problem. Leaving original code + // below for posterity because a simple and reliable test could not be found to trigger + // the behavior. + // debug_assert!( + // this.write_buf.is_empty(), + // "dispatcher should not be in keep-alive phase if write_buf is not empty", + // ); // keep-alive timer has timed out if timer.as_mut().poll(cx).is_ready() { From 151a15da74d32fa28a0c5c5bb5f8b533c7fd608c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 00:21:49 +0000 Subject: [PATCH 381/861] prepare actix-http release 3.0.0-rc.4 --- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 6 ++++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 17 insertions(+), 11 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index a006df953..6e7f7402e 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,7 +22,7 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-rc.3" +actix-http = "3.0.0-rc.4" actix-service = "2" actix-utils = "3" actix-web = { version = "4.0.0-rc.3", default-features = false } diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 2fb4a4f77..7d310aef9 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-rc.3" +actix-http = "3.0.0-rc.4" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c8581c0b5..97ea7dd94 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-rc.4 - 2022-02-22 +- Fix h1 dispatcher panic. [1ce58ecb] + +[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca + + ## 3.0.0-rc.3 - 2022-02-16 - No significant changes since `3.0.0-rc.2`. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 30ac4ce3a..b661e2512 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-rc.3" +version = "3.0.0-rc.4" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index c1aae63e1..137b94f3a 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.3)](https://docs.rs/actix-http/3.0.0-rc.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.4)](https://docs.rs/actix-http/3.0.0-rc.4) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.4) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 91cc96904..fe7320af2 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-rc.3" +actix-http = "3.0.0-rc.4" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 21aeec1da..19b67cc7f 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,7 +29,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" -actix-http = "3.0.0-rc.3" +actix-http = "3.0.0-rc.4" actix-http-test = "3.0.0-beta.13" actix-rt = "2.1" actix-service = "2.0.0" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 121c86eb7..251a03f02 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.5" -actix-http = "3.0.0-rc.3" +actix-http = "3.0.0-rc.4" actix-web = { version = "4.0.0-rc.3", default-features = false } bytes = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 938412090..145fa13a8 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,7 +71,7 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] } actix-router = "0.5.0-rc.3" actix-web-codegen = { version = "0.5.0-rc.2", optional = true } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7076897b1..c8e1cbc60 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" actix-service = "2.0.0" -actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] } +actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,7 +93,7 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-rc.3", features = ["openssl"] } +actix-http = { version = "3.0.0-rc.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } From 5aa6f713c73ad91f0f062730708dca22a4f7b440 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 06:23:01 +0000 Subject: [PATCH 382/861] update errorhandlers migration guide --- actix-web/MIGRATION-4.0.md | 82 +++++++++++++++++++----- actix-web/src/middleware/err_handlers.rs | 36 +++++++++-- actix-web/src/test/test_utils.rs | 2 +- 3 files changed, 98 insertions(+), 22 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index b5109e3f2..5bdf7d312 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -3,8 +3,7 @@ This guide walks you through the process of migrating from v3.x.y to v4.x.y. If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder. -This document is not designed to be exhaustive - it focuses on the most significant changes coming in v4. -You can find an exhaustive changelog in [CHANGES.md](./CHANGES.md), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. +This document is not designed to be exhaustive—it focuses on the most significant changes coming in v4. You can find an exhaustive changelog in [CHANGES.md](./CHANGES.md), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. @@ -39,8 +38,9 @@ The MSRV of Actix Web has been raised from 1.42 to 1.54. ## Tokio v1 Ecosystem -Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem. -`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly - if they are using an older version of `tokio`, check if an update is available. +Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem. + +`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly—if they are using an older version of `tokio`, check if an update is available. The following command can help you to identify these dependencies: ```sh @@ -59,8 +59,8 @@ Lots of modules have been re-organized in this release. If a compile error refer ## `NormalizePath` Middleware :warning: -The default `NormalizePath` behavior now strips trailing slashes by default. -This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed - the discrepancy has now been fixed. +The default `NormalizePath` behavior now strips trailing slashes by default. This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed. The discrepancy has now been resolved. + As a consequence of this change, routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. Calling `NormalizePath::default()` will log a warning. We suggest to use `new` or `trim`. ```diff @@ -103,8 +103,7 @@ The `compress` feature flag has been split into more granular feature flags, one ## `web::Path` -The inner field for `web::Path` is now private. -It was causing too many issues when used with inner tuple types due to its `Deref` implementation. +The inner field for `web::Path` is now private. It was causing ambiguity when trying to use tuple indexing due to its `Deref` implementation. ```diff - async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) { @@ -118,7 +117,7 @@ Actix Web now depends on version 0.20 of `rustls`. As a result, the server confi ## Removed `awc` Client Re-export -Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence - its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule. +Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence—its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule. ```diff - use actix_web::client::Client; @@ -134,11 +133,11 @@ Actix Web's sister crate `awc` is no longer re-exported through the `client` mod + use use actix_test::start; ``` -`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above). +`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above). ## Header APIs -Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. +Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. In short, "insert" always indicates that any existing headers with the same name are overridden, while "append" is used for adding with no removal (e.g. multi-valued headers). @@ -155,7 +154,7 @@ For request and response builder APIs, the new methods provide a unified interfa + .insert_header(ContentType::json()) ``` -We chose to deprecate most of the old methods instead of removing them immediately - the warning notes will guide you on how to update. +We chose to deprecate most of the old methods instead of removing them immediately—the warning notes will guide you on how to update. ## Response Body Types @@ -178,16 +177,65 @@ We have boosted the quality and completeness of the documentation for all items ### `BoxBody` -`BoxBody` is a new type-erased body type. It's used for all error response bodies. -Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type. +`BoxBody` is a new type-erased body type. + +It can be useful when writing handlers, responders, and middleware when you want to trade a (very) small amount of performance for a simpler type. + +Creating a boxed body is done most efficiently by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type. ### `EitherBody` -`EitherBody` is a new "either" type that is particularly useful in middlewares that can bail early, returning their own response plus body type. +`EitherBody` is a new "either" type that implements `MessageBody` + +It is particularly useful in middleware that can bail early, returning their own response plus body type. By default the "right" variant is `BoxBody` (i.e., `EitherBody` === `EitherBody`) but it can be anything that implements `MessageBody`. + +For example, it will be common among middleware which value performance of the hot path to use: + +```rust +type Response = Result>, Error> +``` + +This can be read (ignoring the `Result`) as "resolves with a `ServiceResponse` that is either the inner service's `B` body type or a boxed body type from elsewhere, likely constructed within the middleware itself". Of course, if your middleware contains only simple string other/error responses, it's possible to use them without boxes at the cost of a less simple implementation: + +```rust +type Response = Result>, Error> +``` ### Error Handlers -TODO In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types. +`ErrorHandlers` is a commonly used middleware that has changed in design slightly due to the other body type changes. + +In particular, an implicit `EitherBody` is used in the `ErrorHandlerResponse` type. An `ErrorHandlerResponse` now expects a `ServiceResponse>` to be returned within response variants. The following is a migration for an error handler that **only modifies** the response argument (left body). + +```diff + fn add_error_header(mut res: ServiceResponse) -> Result, Error> { + res.response_mut().headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("Error"), + ); +- Ok(ErrorHandlerResponse::Response(res)) ++ Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } +``` + +The following is a migration for an error handler that creates a new response instead (right body). + +```diff + fn error_handler(res: ServiceResponse) -> Result, Error> { +- let req = res.request().clone(); ++ let (req, _res) = res.into_parts(); + + let res = actix_files::NamedFile::open("./templates/404.html")? + .set_status_code(StatusCode::NOT_FOUND) +- .into_response(&req)? +- .into_body(); ++ .into_response(&req); + +- let res = ServiceResponse::new(req, res); ++ let res = ServiceResponse::new(req, res).map_into_right_body(); + Ok(ErrorHandlerResponse::Response(res)) + } +``` ## Middleware Trait APIs @@ -251,7 +299,7 @@ You may need to review the [guidance on shared mutable state](https://docs.rs/ac Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular: - all traits necessary for creating middlewares are now re-exported through the `dev` modules; -- `#[actix_web::test]` now exists for async test definitions. +- `#[actix_web::test]` now exists for async test definitions. Relying on these re-exports will ease the transition to future versions of Actix Web. diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs index bde054330..f60e5c4c9 100644 --- a/actix-web/src/middleware/err_handlers.rs +++ b/actix-web/src/middleware/err_handlers.rs @@ -185,11 +185,13 @@ mod tests { use super::*; use crate::{ + body, http::{ header::{HeaderValue, CONTENT_TYPE}, StatusCode, }, test::{self, TestRequest}, + ResponseError, }; #[actix_rt::test] @@ -245,9 +247,7 @@ mod tests { #[actix_rt::test] async fn changes_body_type() { #[allow(clippy::unnecessary_wraps)] - fn error_handler( - res: ServiceResponse, - ) -> Result> { + fn error_handler(res: ServiceResponse) -> Result> { let (req, res) = res.into_parts(); let res = res.set_body(Bytes::from("sorry, that's no bueno")); @@ -270,5 +270,33 @@ mod tests { assert_eq!(test::read_body(res).await, "sorry, that's no bueno"); } - // TODO: test where error is thrown + #[actix_rt::test] + async fn error_thrown() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler(_res: ServiceResponse) -> Result> { + Err(crate::error::ErrorInternalServerError( + "error in error handler", + )) + } + + let srv = test::simple_service(StatusCode::BAD_REQUEST); + + let mw = ErrorHandlers::new() + .handler(StatusCode::BAD_REQUEST, error_handler) + .new_transform(srv.into_service()) + .await + .unwrap(); + + let err = mw + .call(TestRequest::default().to_srv_request()) + .await + .unwrap_err(); + let res = err.error_response(); + + assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + body::to_bytes(res.into_body()).await.unwrap(), + "error in error handler" + ); + } } diff --git a/actix-web/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs index 8207ce270..6f0926f35 100644 --- a/actix-web/src/test/test_utils.rs +++ b/actix-web/src/test/test_utils.rs @@ -89,7 +89,7 @@ where /// ``` /// /// # Panics -/// Panics if service call returns error. +/// Panics if service call returns error. To handle errors use `app.call(req)`. pub async fn call_service(app: &S, req: R) -> S::Response where S: Service, Error = E>, From 11bfa849262aa1ec8ccff7b633e6ee27e153f3f3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 07:06:36 +0000 Subject: [PATCH 383/861] rename simple_service to status_service (#2659) --- actix-test/src/lib.rs | 2 +- actix-web/CHANGES.md | 3 +++ actix-web/src/middleware/err_handlers.rs | 9 ++++----- actix-web/src/test/mod.rs | 4 ++-- actix-web/src/test/test_services.rs | 16 ++++++++++++---- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index d44bc7a45..5efd9758e 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -43,7 +43,7 @@ pub use actix_http_test::unused_addr; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; pub use actix_web::test::{ call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service, - read_body, read_body_json, simple_service, TestRequest, + read_body, read_body_json, status_service, TestRequest, }; use actix_web::{ body::MessageBody, diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index ba29cdaa2..afdc28b6c 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Rename `test::{simple_service => status_service}`. [#2659] + +[#2659]: https://github.com/actix/actix-web/pull/2659 ## 4.0.0-rc.3 - 2022-02-08 diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs index f60e5c4c9..f74220cd2 100644 --- a/actix-web/src/middleware/err_handlers.rs +++ b/actix-web/src/middleware/err_handlers.rs @@ -191,7 +191,6 @@ mod tests { StatusCode, }, test::{self, TestRequest}, - ResponseError, }; #[actix_rt::test] @@ -205,7 +204,7 @@ mod tests { Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } - let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) @@ -232,7 +231,7 @@ mod tests { )) } - let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) @@ -258,7 +257,7 @@ mod tests { Ok(ErrorHandlerResponse::Response(res)) } - let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR); + let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR); let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler) @@ -279,7 +278,7 @@ mod tests { )) } - let srv = test::simple_service(StatusCode::BAD_REQUEST); + let srv = test::status_service(StatusCode::BAD_REQUEST); let mw = ErrorHandlers::new() .handler(StatusCode::BAD_REQUEST, error_handler) diff --git a/actix-web/src/test/mod.rs b/actix-web/src/test/mod.rs index a29dfc437..9c6121151 100644 --- a/actix-web/src/test/mod.rs +++ b/actix-web/src/test/mod.rs @@ -5,7 +5,7 @@ //! //! # Off-The-Shelf Test Services //! - [`ok_service`] -//! - [`simple_service`] +//! - [`status_service`] //! //! # Calling Test Service //! - [`TestRequest`] @@ -27,7 +27,7 @@ mod test_utils; pub use self::test_request::TestRequest; #[allow(deprecated)] -pub use self::test_services::{default_service, ok_service, simple_service}; +pub use self::test_services::{default_service, ok_service, simple_service, status_service}; #[allow(deprecated)] pub use self::test_utils::{ call_and_read_body, call_and_read_body_json, call_service, init_service, read_body, diff --git a/actix-web/src/test/test_services.rs b/actix-web/src/test/test_services.rs index b4810cfd8..e6feea82d 100644 --- a/actix-web/src/test/test_services.rs +++ b/actix-web/src/test/test_services.rs @@ -10,11 +10,11 @@ use crate::{ /// Creates service that always responds with `200 OK` and no body. pub fn ok_service( ) -> impl Service, Error = Error> { - simple_service(StatusCode::OK) + status_service(StatusCode::OK) } /// Creates service that always responds with given status code and no body. -pub fn simple_service( +pub fn status_service( status_code: StatusCode, ) -> impl Service, Error = Error> { fn_service(move |req: ServiceRequest| { @@ -23,9 +23,17 @@ pub fn simple_service( } #[doc(hidden)] -#[deprecated(since = "4.0.0", note = "Renamed to `simple_service`.")] +#[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")] +pub fn simple_service( + status_code: StatusCode, +) -> impl Service, Error = Error> { + status_service(status_code) +} + +#[doc(hidden)] +#[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")] pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { - simple_service(status_code) + status_service(status_code) } From 218e34ee1763f421b7b1e81a3f83034028e29be5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 07:04:23 +0000 Subject: [PATCH 384/861] fix http error debug impl --- actix-http/src/error.rs | 7 ++++--- actix-web/src/response/response.rs | 6 ------ awc/src/client/pool.rs | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 52b953421..2802d57a4 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -108,8 +108,10 @@ pub(crate) enum Kind { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: more detail - f.write_str("actix_http::Error") + f.debug_struct("actix_http::Error") + .field("kind", &self.inner.kind) + .field("cause", &self.inner.cause) + .finish() } } @@ -386,7 +388,6 @@ pub enum DispatchError { impl StdError for DispatchError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { - // TODO: error source extraction? DispatchError::Service(_res) => None, DispatchError::Body(err) => Some(&**err), DispatchError::Io(err) => Some(err), diff --git a/actix-web/src/response/response.rs b/actix-web/src/response/response.rs index 6449c586d..630acc3f2 100644 --- a/actix-web/src/response/response.rs +++ b/actix-web/src/response/response.rs @@ -323,12 +323,6 @@ impl From for HttpResponse { impl From> for Response { fn from(res: HttpResponse) -> Self { // this impl will always be called as part of dispatcher - - // TODO: expose cause somewhere? - // if let Some(err) = res.error { - // return Response::from_error(err); - // } - res.res } } diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs index 9d130412b..cc3e4d7c0 100644 --- a/awc/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -232,7 +232,7 @@ where None => { let (io, proto) = connector.call(req).await?; - // TODO: remove when http3 is added in support. + // NOTE: remove when http3 is added in support. assert!(proto != Protocol::Http3); if proto == Protocol::Http1 { From a6f27baff1fd0c7e98f1c966799d2e4779408873 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 07:05:28 +0000 Subject: [PATCH 385/861] flesh out Responder docs --- actix-web/Cargo.toml | 1 + actix-web/src/response/responder.rs | 49 ++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 145fa13a8..b7410b5ef 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -77,6 +77,7 @@ actix-web-codegen = { version = "0.5.0-rc.2", optional = true } ahash = "0.7" bytes = "1" +bytestring = "1" cfg-if = "1" cookie = { version = "0.16", features = ["percent-encode"], optional = true } derive_more = "0.99.5" diff --git a/actix-web/src/response/responder.rs b/actix-web/src/response/responder.rs index c88faec89..da8091981 100644 --- a/actix-web/src/response/responder.rs +++ b/actix-web/src/response/responder.rs @@ -7,14 +7,35 @@ use actix_http::{ }; use bytes::{Bytes, BytesMut}; -use crate::{Error, HttpRequest, HttpResponse}; - use super::CustomizeResponder; +use crate::{Error, HttpRequest, HttpResponse}; /// Trait implemented by types that can be converted to an HTTP response. /// -/// Any types that implement this trait can be used in the return type of a handler. -// # TODO: more about implementation notes and foreign impls +/// Any types that implement this trait can be used in the return type of a handler. Since handlers +/// will only have one return type, it is idiomatic to use opaque return types `-> impl Responder`. +/// +/// # Implementations +/// It is often not required to implement `Responder` for your own types due to a broad base of +/// built-in implementations: +/// - `HttpResponse` and `HttpResponseBuilder` +/// - `Option` where `R: Responder` +/// - `Result` where `R: Responder` and [`E: ResponseError`](crate::ResponseError) +/// - `(R, StatusCode) where `R: Responder` +/// - `&'static str`, `String`, `&'_ String`, `Cow<'_, str>`, [`ByteString`](bytestring::ByteString) +/// - `&'static [u8]`, `Vec`, `Bytes`, `BytesMut` +/// - [`Json`](crate::web::Json) and [`Form`](crate::web::Form) where `T: Serialize` +/// - [`Either`](crate::web::Either) where `L: Serialize` and `R: Serialize` +/// - [`CustomizeResponder`] +/// - [`actix_files::NamedFile`](https://docs.rs/actix-files/latest/actix_files/struct.NamedFile.html) +/// - [Experimental responders from `actix-web-lab`](https://docs.rs/actix-web-lab/latest/actix_web_lab/respond/index.html) +/// - Third party integrations may also have implemented `Responder` where appropriate. For example, +/// HTML templating engines. +/// +/// # Customizing Responder Output +/// Calling [`.customize()`](Responder::customize) on any responder type will wrap it in a +/// [`CustomizeResponder`] capable of overriding various parts of the response such as the status +/// code and header map. pub trait Responder { type Body: MessageBody + 'static; @@ -23,7 +44,7 @@ pub trait Responder { /// Wraps responder to allow alteration of its response. /// - /// See [`CustomizeResponder`] docs for its capabilities. + /// See [`CustomizeResponder`] docs for more details on its capabilities. /// /// # Examples /// ``` @@ -84,11 +105,8 @@ impl Responder for actix_http::ResponseBuilder { } } -impl Responder for Option -where - T: Responder, -{ - type Body = EitherBody; +impl Responder for Option { + type Body = EitherBody; fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { @@ -98,12 +116,12 @@ where } } -impl Responder for Result +impl Responder for Result where - T: Responder, + R: Responder, E: Into, { - type Body = EitherBody; + type Body = EitherBody; fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { @@ -113,8 +131,8 @@ where } } -impl Responder for (T, StatusCode) { - type Body = T::Body; +impl Responder for (R, StatusCode) { + type Body = R::Body; fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); @@ -147,6 +165,7 @@ impl_responder_by_forward_into_base_response!(BytesMut); impl_responder_by_forward_into_base_response!(&'static str); impl_responder_by_forward_into_base_response!(String); +impl_responder_by_forward_into_base_response!(bytestring::ByteString); macro_rules! impl_into_string_responder { ($res:ty) => { From 53509a53618deaa33f6ea8598434adedabd89247 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 07:07:08 +0000 Subject: [PATCH 386/861] ignore all http1 connection headers in h2 --- actix-http/src/h2/dispatcher.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index d528bec96..ce1be537f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -25,7 +25,9 @@ use pin_project_lite::pin_project; use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, - header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, + header::{ + HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, + }, service::HttpFlow, Extensions, OnConnectData, Payload, Request, Response, ResponseHead, }; @@ -306,13 +308,22 @@ fn prepare_response( // copy headers for (key, value) in head.headers.iter() { - match *key { - // TODO: consider skipping other headers according to: - // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 - // omit HTTP/1.x only headers - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_LENGTH if skip_len => continue, - DATE => has_date = true, + match key { + // omit HTTP/1.x only headers according to: + // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 + &CONNECTION | &TRANSFER_ENCODING | &UPGRADE => continue, + + &CONTENT_LENGTH if skip_len => continue, + &DATE => has_date = true, + + // omit HTTP/1.x only headers according to: + // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 + hdr if hdr == HeaderName::from_static("keep-alive") + || hdr == HeaderName::from_static("proxy-connection") => + { + continue + } + _ => {} } From 1c1d6477efb1b6aebf2a6bc312c7b827cbb79637 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 07:11:16 +0000 Subject: [PATCH 387/861] remove legacy ws test --- actix-http/src/ws/mask.rs | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 20b4372a0..be72e5631 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -47,40 +47,6 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { mod tests { use super::*; - // legacy test from old apply mask test. kept for now for back compat test. - // TODO: remove it and favor the other test. - #[test] - fn test_apply_mask_legacy() { - let mask = [0x6d, 0xb6, 0xb2, 0x80]; - - let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, - 0x12, 0x03, - ]; - - // Check masking with proper alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked, mask); - - let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast, mask); - - assert_eq!(masked, masked_fast); - } - - // Check masking without alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked[1..], mask); - - let mut masked_fast = unmasked; - apply_mask(&mut masked_fast[1..], mask); - - assert_eq!(masked, masked_fast); - } - } - #[test] fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; From ad38973767f38eba50cd52bb37dc1a0919185045 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 08:45:28 +0000 Subject: [PATCH 388/861] move blocking error to web (#2660) --- actix-files/src/chunked.rs | 5 ++--- actix-http/CHANGES.md | 4 ++++ actix-http/src/encoding/decoder.rs | 16 +++++++++++++--- actix-http/src/encoding/encoder.rs | 14 ++++++++------ actix-http/src/error.rs | 23 ++++------------------- actix-http/tests/test_server.rs | 3 ++- actix-web/CHANGES.md | 1 + actix-web/src/error/error.rs | 1 - actix-web/src/error/mod.rs | 10 +++++++++- actix-web/src/error/response_error.rs | 20 +++++++++----------- actix-web/src/http/mod.rs | 1 - 11 files changed, 52 insertions(+), 46 deletions(-) diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 3ee2ee072..241b4dccb 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -81,7 +81,7 @@ async fn chunked_read_file_callback( ) -> Result<(File, Bytes), Error> { use io::{Read as _, Seek as _}; - let res = actix_web::rt::task::spawn_blocking(move || { + let res = actix_web::web::block(move || { let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; @@ -94,8 +94,7 @@ async fn chunked_read_file_callback( Ok((file, Bytes::from(buf))) } }) - .await - .map_err(|_| actix_web::error::BlockingError)??; + .await??; Ok(res) } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 97ea7dd94..0561e82fc 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Removed +- `error::BlockingError` [#2660] + +[#2660]: https://github.com/actix/actix-web/pull/2660 ## 3.0.0-rc.4 - 2022-02-22 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 2ed7be899..06b672fd8 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -19,7 +19,7 @@ use zstd::stream::write::Decoder as ZstdDecoder; use crate::{ encoding::Writer, - error::{BlockingError, PayloadError}, + error::PayloadError, header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, }; @@ -47,14 +47,17 @@ where ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new( brotli::DecompressorWriter::new(Writer::new(), 8_096), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ZlibDecoder::new(Writer::new()), ))), + #[cfg(feature = "compress-gzip")] ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new( Writer::new(), )))), + #[cfg(feature = "compress-zstd")] ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new( ZstdDecoder::new(Writer::new()).expect( @@ -98,8 +101,12 @@ where loop { if let Some(ref mut fut) = this.fut { - let (chunk, decoder) = - ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| { + PayloadError::Io(io::Error::new( + io::ErrorKind::Other, + "Blocking task was cancelled unexpectedly", + )) + })??; *this.decoder = Some(decoder); this.fut.take(); @@ -159,10 +166,13 @@ where enum ContentDecoder { #[cfg(feature = "compress-gzip")] Deflate(Box>), + #[cfg(feature = "compress-gzip")] Gzip(Box>), + #[cfg(feature = "compress-brotli")] Brotli(Box>), + // We need explicit 'static lifetime here because ZstdDecoder need lifetime // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` #[cfg(feature = "compress-zstd")] diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 2f104ee8f..0c81ffe1b 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -23,7 +23,6 @@ use zstd::stream::write::Encoder as ZstdEncoder; use super::Writer; use crate::{ body::{self, BodySize, MessageBody}, - error::BlockingError, header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, ResponseHead, StatusCode, }; @@ -173,7 +172,12 @@ where if let Some(ref mut fut) = this.fut { let mut encoder = ready!(Pin::new(fut).poll(cx)) - .map_err(|_| EncoderError::Blocking(BlockingError))? + .map_err(|_| { + EncoderError::Io(io::Error::new( + io::ErrorKind::Other, + "Blocking task was cancelled unexpectedly", + )) + })? .map_err(EncoderError::Io)?; let chunk = encoder.take(); @@ -400,12 +404,11 @@ fn new_brotli_compressor() -> Box> { #[derive(Debug, Display)] #[non_exhaustive] pub enum EncoderError { + /// Wrapped body stream error. #[display(fmt = "body")] Body(Box), - #[display(fmt = "blocking")] - Blocking(BlockingError), - + /// Generic I/O error. #[display(fmt = "io")] Io(io::Error), } @@ -414,7 +417,6 @@ impl StdError for EncoderError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { EncoderError::Body(err) => Some(&**err), - EncoderError::Blocking(err) => Some(err), EncoderError::Io(err) => Some(err), } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 2802d57a4..3fce0a60b 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -51,7 +51,7 @@ impl Error { Self::new(Kind::SendResponse) } - #[allow(unused)] // reserved for future use (TODO: remove allow when being used) + #[allow(unused)] // available for future use pub(crate) fn new_io() -> Self { Self::new(Kind::Io) } @@ -252,12 +252,6 @@ impl From for Response { } } -/// A set of errors that can occur running blocking tasks in thread pool. -#[derive(Debug, Display, Error)] -#[display(fmt = "Blocking thread pool is gone")] -// TODO: non-exhaustive -pub struct BlockingError; - /// A set of errors that can occur during payload parsing. #[derive(Debug, Display)] #[non_exhaustive] @@ -295,13 +289,13 @@ impl std::error::Error for PayloadError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { PayloadError::Incomplete(None) => None, - PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error), + PayloadError::Incomplete(Some(err)) => Some(err), PayloadError::EncodingCorrupted => None, PayloadError::Overflow => None, PayloadError::UnknownLength => None, #[cfg(feature = "http2")] - PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error), - PayloadError::Io(err) => Some(err as &dyn std::error::Error), + PayloadError::Http2Payload(err) => Some(err), + PayloadError::Io(err) => Some(err), } } } @@ -325,15 +319,6 @@ impl From for PayloadError { } } -impl From for PayloadError { - fn from(_: BlockingError) -> Self { - PayloadError::Io(io::Error::new( - io::ErrorKind::Other, - "Operation is canceled", - )) - } -} - impl From for Error { fn from(err: PayloadError) -> Self { Self::new_payload().with_cause(err) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 1b5de3425..e8d103c96 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -850,7 +850,8 @@ async fn not_modified_spec_h1() { Some(&header::HeaderValue::from_static("4")), ); // server does not prevent payload from being sent but clients may choose not to read it - // TODO: this is probably a bug, especially since CL header can differ in length from the body + // TODO: this is probably a bug in the client, especially since CL header can differ in length + // from the body assert!(!srv.load_body(res).await.unwrap().is_empty()); // TODO: add stream response tests diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index afdc28b6c..ff4823149 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed - Rename `test::{simple_service => status_service}`. [#2659] [#2659]: https://github.com/actix/actix-web/pull/2659 diff --git a/actix-web/src/error/error.rs b/actix-web/src/error/error.rs index 8450bed35..3d3978dde 100644 --- a/actix-web/src/error/error.rs +++ b/actix-web/src/error/error.rs @@ -47,7 +47,6 @@ impl fmt::Debug for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - // TODO: populate if replacement for Box is found None } } diff --git a/actix-web/src/error/mod.rs b/actix-web/src/error/mod.rs index 64df9f553..6095cd5d2 100644 --- a/actix-web/src/error/mod.rs +++ b/actix-web/src/error/mod.rs @@ -6,7 +6,7 @@ // // See pub use actix_http::error::{ - BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, + ContentTypeError, DispatchError, HttpError, ParseError, PayloadError, }; use derive_more::{Display, Error, From}; @@ -33,6 +33,14 @@ pub(crate) use macros::{downcast_dyn, downcast_get_type_id}; /// This type alias is generally used to avoid writing out `actix_http::Error` directly. pub type Result = std::result::Result; +/// An error representing a problem running a blocking task on a thread pool. +#[derive(Debug, Display, Error)] +#[display(fmt = "Blocking thread pool is shut down unexpectedly")] +#[non_exhaustive] +pub struct BlockingError; + +impl ResponseError for crate::error::BlockingError {} + /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, Error, From)] #[non_exhaustive] diff --git a/actix-web/src/error/response_error.rs b/actix-web/src/error/response_error.rs index e0b4af44c..0b8a82ce8 100644 --- a/actix-web/src/error/response_error.rs +++ b/actix-web/src/error/response_error.rs @@ -6,20 +6,22 @@ use std::{ io::{self, Write as _}, }; -use actix_http::{ - body::BoxBody, - header::{self, TryIntoHeaderValue}, - Response, StatusCode, -}; +use actix_http::Response; use bytes::BytesMut; use crate::{ + body::BoxBody, error::{downcast_dyn, downcast_get_type_id}, - helpers, HttpResponse, + helpers, + http::{ + header::{self, TryIntoHeaderValue}, + StatusCode, + }, + HttpResponse, }; /// Errors that can generate responses. -// TODO: add std::error::Error bound when replacement for Box is found +// TODO: flesh out documentation pub trait ResponseError: fmt::Debug + fmt::Display { /// Returns appropriate status code for error. /// @@ -73,7 +75,6 @@ impl ResponseError for std::str::Utf8Error { impl ResponseError for std::io::Error { fn status_code(&self) -> StatusCode { - // TODO: decide if these errors should consider not found or permission errors match self.kind() { io::ErrorKind::NotFound => StatusCode::NOT_FOUND, io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, @@ -86,7 +87,6 @@ impl ResponseError for actix_http::error::HttpError {} impl ResponseError for actix_http::Error { fn status_code(&self) -> StatusCode { - // TODO: map error kinds to status code better StatusCode::INTERNAL_SERVER_ERROR } @@ -107,8 +107,6 @@ impl ResponseError for actix_http::error::ParseError { } } -impl ResponseError for actix_http::error::BlockingError {} - impl ResponseError for actix_http::error::PayloadError { fn status_code(&self) -> StatusCode { match *self { diff --git a/actix-web/src/http/mod.rs b/actix-web/src/http/mod.rs index 91c0ca377..2866e1a2c 100644 --- a/actix-web/src/http/mod.rs +++ b/actix-web/src/http/mod.rs @@ -2,5 +2,4 @@ pub mod header; -// TODO: figure out how best to expose http::Error vs actix_http::Error pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version}; From 75e6ffb057f79d7f1439bcd9fdc57c371aba36c3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 11:38:25 +0000 Subject: [PATCH 389/861] prepare actix-router release 0.5.0 (#2658) --- actix-router/CHANGES.md | 161 +++++++++++++++++++++++++---------- actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- 4 files changed, 120 insertions(+), 47 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index ff9d8b4ab..f4b2022f3 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -3,6 +3,77 @@ ## Unreleased - 2021-xx-xx +## 0.5.0 - 2022-02-22 +### Added +- `Resource` is now implemented for `&mut Path<_>` and `RefMut>`. [#2568] +- Add `Path::as_str`. [#2590] +- Add `ResourceDef::set_name`. [#373][net#373] +- Add `RouterBuilder::push`. [#2612] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372] +- Introduce `ResourceDef::join`. [#380][net#380] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373] +- Support `build_resource_path` on multi-pattern resources. [#2356] +- Support multi-pattern prefixes and joins. [#2356] + +### Changed +- `Quoter::requote` now returns `Option>`. [#2613] +- `Resource` trait now uses an associated type, `Path`, instead of a generic parameter. [#2568] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373] +- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] +- Deprecate `Path::path`. [#2590] +- Disallow prefix routes with tail segments. [#379][net#379] +- Enforce path separators on dynamic prefixes. [#378][net#378] +- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] +- Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370][net#370] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371] +- Rename `Router::{*_checked => *_fn}`. [#373][net#373] +- Replace `Option` with `U` in `Router` API. [#2612] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373] +- Minimum supported Rust version (MSRV) is now 1.54. + +### Fixed +- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566] +- Fix `ResourceDef` `PartialEq` implementation. [#373][net#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368] +- Improve malformed path error message. [#384][net#384] +- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612] + +### Removed +- `ResourceDef::name_mut`. [#373][net#373] +- Unused `ResourceInfo`. [#2612] + +[#2355]: https://github.com/actix/actix-web/pull/2355 +[#2356]: https://github.com/actix/actix-web/pull/2356 +[#2566]: https://github.com/actix/actix-net/pull/2566 +[#2568]: https://github.com/actix/actix-web/pull/2568 +[#2590]: https://github.com/actix/actix-web/pull/2590 +[#2612]: https://github.com/actix/actix-web/pull/2612 +[#2613]: https://github.com/actix/actix-web/pull/2613 +[net#366]: https://github.com/actix/actix-net/pull/366 +[net#368]: https://github.com/actix/actix-net/pull/368 +[net#368]: https://github.com/actix/actix-net/pull/368 +[net#370]: https://github.com/actix/actix-net/pull/370 +[net#371]: https://github.com/actix/actix-net/pull/371 +[net#372]: https://github.com/actix/actix-net/pull/372 +[net#373]: https://github.com/actix/actix-net/pull/373 +[net#378]: https://github.com/actix/actix-net/pull/378 +[net#379]: https://github.com/actix/actix-net/pull/379 +[net#380]: https://github.com/actix/actix-net/pull/380 +[net#384]: https://github.com/actix/actix-net/pull/384 + + +

+0.5.0 Pre-Releases + ## 0.5.0-rc.3 - 2022-01-31 - Remove unused `ResourceInfo`. [#2612] - Add `RouterBuilder::push`. [#2612] @@ -41,10 +112,10 @@ ## 0.5.0-beta.2 - 2021-09-09 -- Introduce `ResourceDef::join`. [#380] -- Disallow prefix routes with tail segments. [#379] -- Enforce path separators on dynamic prefixes. [#378] -- Improve malformed path error message. [#384] +- Introduce `ResourceDef::join`. [#380][net#380] +- Disallow prefix routes with tail segments. [#379][net#379] +- Enforce path separators on dynamic prefixes. [#378][net#378] +- Improve malformed path error message. [#384][net#384] - Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] - Prefix segments with trailing slashes define a trailing empty segment. [#2355] - Support multi-pattern prefixes and joins. [#2356] @@ -52,52 +123,54 @@ - Support `build_resource_path` on multi-pattern resources. [#2356] - Minimum supported Rust version (MSRV) is now 1.51. -[#378]: https://github.com/actix/actix-net/pull/378 -[#379]: https://github.com/actix/actix-net/pull/379 -[#380]: https://github.com/actix/actix-net/pull/380 -[#384]: https://github.com/actix/actix-net/pull/384 +[net#378]: https://github.com/actix/actix-net/pull/378 +[net#379]: https://github.com/actix/actix-net/pull/379 +[net#380]: https://github.com/actix/actix-net/pull/380 +[net#384]: https://github.com/actix/actix-net/pull/384 [#2355]: https://github.com/actix/actix-web/pull/2355 [#2356]: https://github.com/actix/actix-web/pull/2356 ## 0.5.0-beta.1 - 2021-07-20 -- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366] -- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373] -- Fix segment interpolation leaving `Path` in unintended state after matching. [#368] -- Fix `ResourceDef` `PartialEq` implementation. [#373] -- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372] -- Implement `IntoPatterns` for `bytestring::ByteString`. [#372] -- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370] -- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371] -- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373] -- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371] -- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373] -- Rename `ResourceDef::{match_path => capture_match_info}`. [#373] -- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373] -- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373] -- Rename `Router::{*_checked => *_fn}`. [#373] -- Return type of `ResourceDef::name` is now `Option<&str>`. [#373] -- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373] +- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366] +- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373] +- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368] +- Fix `ResourceDef` `PartialEq` implementation. [#373][net#373] +- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372] +- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372] +- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370][net#370] +- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373] +- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371] +- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373] +- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373] +- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373] +- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373][net#373] +- Rename `Router::{*_checked => *_fn}`. [#373][net#373] +- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373] +- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373] -[#368]: https://github.com/actix/actix-net/pull/368 -[#366]: https://github.com/actix/actix-net/pull/366 -[#368]: https://github.com/actix/actix-net/pull/368 -[#370]: https://github.com/actix/actix-net/pull/370 -[#371]: https://github.com/actix/actix-net/pull/371 -[#372]: https://github.com/actix/actix-net/pull/372 -[#373]: https://github.com/actix/actix-net/pull/373 +[net#368]: https://github.com/actix/actix-net/pull/368 +[net#366]: https://github.com/actix/actix-net/pull/366 +[net#368]: https://github.com/actix/actix-net/pull/368 +[net#370]: https://github.com/actix/actix-net/pull/370 +[net#371]: https://github.com/actix/actix-net/pull/371 +[net#372]: https://github.com/actix/actix-net/pull/372 +[net#373]: https://github.com/actix/actix-net/pull/373 + +
## 0.4.0 - 2021-06-06 -- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357] -- Path tail patterns now match new lines (`\n`) in request URL. [#360] -- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359] -- Methods `Path::{add, add_static}` now take `impl Into>`. [#345] +- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357][net#357] +- Path tail patterns now match new lines (`\n`) in request URL. [#360][net#360] +- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359][net#359] +- Methods `Path::{add, add_static}` now take `impl Into>`. [#345][net#345] -[#345]: https://github.com/actix/actix-net/pull/345 -[#357]: https://github.com/actix/actix-net/pull/357 -[#359]: https://github.com/actix/actix-net/pull/359 -[#360]: https://github.com/actix/actix-net/pull/360 +[net#345]: https://github.com/actix/actix-net/pull/345 +[net#357]: https://github.com/actix/actix-net/pull/357 +[net#359]: https://github.com/actix/actix-net/pull/359 +[net#360]: https://github.com/actix/actix-net/pull/360 ## 0.3.0 - 2019-12-31 @@ -105,15 +178,15 @@ ## 0.2.7 - 2021-02-06 -- Add `Router::recognize_checked` [#247] +- Add `Router::recognize_checked` [#247][net#247] -[#247]: https://github.com/actix/actix-net/pull/247 +[net#247]: https://github.com/actix/actix-net/pull/247 ## 0.2.6 - 2021-01-09 -- Use `bytestring` version range compatible with Bytes v1.0. [#246] +- Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246] -[#246]: https://github.com/actix/actix-net/pull/246 +[net#246]: https://github.com/actix/actix-net/pull/246 ## 0.2.5 - 2020-09-20 diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 647a34479..502109114 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0-rc.3" +version = "0.5.0" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index e3ff61509..71ec3148f 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" proc-macro = true [dependencies] -actix-router = "0.5.0-rc.3" +actix-router = "0.5.0" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "parsing"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index b7410b5ef..33c897dc7 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -72,7 +72,7 @@ actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] } -actix-router = "0.5.0-rc.3" +actix-router = "0.5.0" actix-web-codegen = { version = "0.5.0-rc.2", optional = true } ahash = "0.7" From ce00c889632ba23ce19745163b8e37bd2690191e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 11:46:51 +0000 Subject: [PATCH 390/861] fix changelog typo --- actix-router/CHANGES.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index f4b2022f3..8e0e4f41e 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -5,29 +5,27 @@ ## 0.5.0 - 2022-02-22 ### Added -- `Resource` is now implemented for `&mut Path<_>` and `RefMut>`. [#2568] - Add `Path::as_str`. [#2590] - Add `ResourceDef::set_name`. [#373][net#373] - Add `RouterBuilder::push`. [#2612] - Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372] - Introduce `ResourceDef::join`. [#380][net#380] - Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373] +- `Resource` is now implemented for `&mut Path<_>` and `RefMut>`. [#2568] - Support `build_resource_path` on multi-pattern resources. [#2356] - Support multi-pattern prefixes and joins. [#2356] ### Changed -- `Quoter::requote` now returns `Option>`. [#2613] -- `Resource` trait now uses an associated type, `Path`, instead of a generic parameter. [#2568] -- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] -- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373] - Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612] - Deprecate `Path::path`. [#2590] - Disallow prefix routes with tail segments. [#379][net#379] - Enforce path separators on dynamic prefixes. [#378][net#378] +- Minimum supported Rust version (MSRV) is now 1.54. - Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] - Prefix segments with trailing slashes define a trailing empty segment. [#2355] +- `Quoter::requote` now returns `Option>`. [#2613] - Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372] -- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370][net#370] +- Rename `Path::{len => segment_count}` to be more descriptive of its purpose. [#370][net#370] - Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373] - Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373] - Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373] @@ -35,17 +33,19 @@ - Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371] - Rename `Router::{*_checked => *_fn}`. [#373][net#373] - Replace `Option` with `U` in `Router` API. [#2612] +- `Resource` trait now uses an associated type, `Path`, instead of a generic parameter. [#2568] +- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356] +- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373] - Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373] - Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373] -- Minimum supported Rust version (MSRV) is now 1.54. ### Fixed -- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566] -- Fix `ResourceDef` `PartialEq` implementation. [#373][net#373] -- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366] +- Fix `ResourceDef`'s `PartialEq` implementation. [#373][net#373] - Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368] - Improve malformed path error message. [#384][net#384] +- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566] - Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612] +- Static patterns in multi-patterns are no longer interpreted as regex. [#366][net#366] ### Removed - `ResourceDef::name_mut`. [#373][net#373] From 10ef9b0751a50f51f8a2bbff60fb4335bfc0d454 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 12:32:06 +0000 Subject: [PATCH 391/861] remove useless doctest main fns --- actix-files/src/named.rs | 20 ++++++++++---------- actix-web/src/app.rs | 10 ++++------ actix-web/src/extract.rs | 18 +++++++----------- actix-web/src/request.rs | 10 ++++------ actix-web/src/resource.rs | 39 ++++++++++++++++++--------------------- 5 files changed, 43 insertions(+), 54 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index baf9b5531..a307c6385 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -96,18 +96,18 @@ impl NamedFile { /// /// # Examples /// ```ignore + /// use std::{ + /// io::{self, Write as _}, + /// env, + /// fs::File + /// }; /// use actix_files::NamedFile; - /// use std::io::{self, Write}; - /// use std::env; - /// use std::fs::File; /// - /// fn main() -> io::Result<()> { - /// let mut file = File::create("foo.txt")?; - /// file.write_all(b"Hello, world!")?; - /// let named_file = NamedFile::from_file(file, "bar.txt")?; - /// # std::fs::remove_file("foo.txt"); - /// Ok(()) - /// } + /// let mut file = File::create("foo.txt")?; + /// file.write_all(b"Hello, world!")?; + /// let named_file = NamedFile::from_file(file, "bar.txt")?; + /// # std::fs::remove_file("foo.txt"); + /// Ok(()) /// ``` pub fn from_file>(file: File, path: P) -> io::Result { let path = path.as_ref().to_path_buf(); diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index be3526393..d2df72714 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -290,12 +290,10 @@ where /// Ok(HttpResponse::Ok().into()) /// } /// - /// fn main() { - /// let app = App::new() - /// .service(web::resource("/index.html").route( - /// web::get().to(index))) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}"); - /// } + /// let app = App::new() + /// .service(web::resource("/index.html").route( + /// web::get().to(index))) + /// .external_resource("youtube", "https://youtube.com/watch/{video_id}"); /// ``` pub fn external_resource(mut self, name: N, url: U) -> Self where diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index f16c29ca5..a8b3d4565 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -118,12 +118,10 @@ pub trait FromRequest: Sized { /// } /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/:first").route( -/// web::post().to(index)) -/// ); -/// } +/// let app = App::new().service( +/// web::resource("/users/:first").route( +/// web::post().to(index)) +/// ); /// ``` impl FromRequest for Option where @@ -205,11 +203,9 @@ where /// } /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/:first").route(web::post().to(index)) -/// ); -/// } +/// let app = App::new().service( +/// web::resource("/users/:first").route(web::post().to(index)) +/// ); /// ``` impl FromRequest for Result where diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index f04e551d4..5545cf982 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -407,12 +407,10 @@ impl Drop for HttpRequest { /// format!("Got thing: {:?}", req) /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/{first}").route( -/// web::get().to(index)) -/// ); -/// } +/// let app = App::new().service( +/// web::resource("/users/{first}").route( +/// web::get().to(index)) +/// ); /// ``` impl FromRequest for HttpRequest { type Error = Error; diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs index 6a01a0496..c5c6701e6 100644 --- a/actix-web/src/resource.rs +++ b/actix-web/src/resource.rs @@ -93,19 +93,17 @@ where /// "Welcome!" /// } /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .route(web::get().to(index)) - /// ) - /// .service( - /// web::resource("/app") - /// .guard(guard::Header("content-type", "text/json")) - /// .route(web::get().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } + /// let app = App::new() + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .route(web::get().to(index)) + /// ) + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/json")) + /// .route(web::get().to(|| HttpResponse::MethodNotAllowed())) + /// ); /// ``` pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(guard)); @@ -137,14 +135,13 @@ where /// ``` /// use actix_web::{web, guard, App}; /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/container/") - /// .route(web::get().to(get_handler)) - /// .route(web::post().to(post_handler)) - /// .route(web::delete().to(delete_handler)) - /// ); - /// } + /// let app = App::new().service( + /// web::resource("/container/") + /// .route(web::get().to(get_handler)) + /// .route(web::post().to(post_handler)) + /// .route(web::delete().to(delete_handler)) + /// ); + /// /// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } /// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } /// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } From 693271e57103f6d96d0d8e7ea1219358365652a6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 12:41:08 +0000 Subject: [PATCH 392/861] add CI job concurrency groups --- .github/workflows/ci-post-merge.yml | 14 ++++++++++++++ .github/workflows/ci.yml | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d37b2c107..15fa4c98b 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -19,6 +19,10 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + env: CI: 1 CARGO_INCREMENTAL: 0 @@ -95,6 +99,11 @@ jobs: ci_feature_powerset_check: name: Verify Feature Combinations runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + steps: - uses: actions/checkout@v2 @@ -156,6 +165,11 @@ jobs: nextest: name: nextest runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f41aa972f..1d3d1c2c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,10 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + env: CI: 1 CARGO_INCREMENTAL: 0 @@ -98,6 +102,11 @@ jobs: rustdoc: name: doc tests runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + steps: - uses: actions/checkout@v2 From 2665357a0c84744f024a2d9e33ef0498903780f9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 12:47:57 +0000 Subject: [PATCH 393/861] fix ci groups --- .github/workflows/ci-post-merge.yml | 6 +++--- .github/workflows/ci.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 15fa4c98b..8057e4e11 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.target.os }} concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} cancel-in-progress: true env: @@ -101,7 +101,7 @@ jobs: runs-on: ubuntu-latest concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} cancel-in-progress: true steps: @@ -167,7 +167,7 @@ jobs: runs-on: ubuntu-latest concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} cancel-in-progress: true steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d3d1c2c8..21658cb33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: ${{ matrix.target.os }} concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} cancel-in-progress: true env: @@ -104,7 +104,7 @@ jobs: runs-on: ubuntu-latest concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} cancel-in-progress: true steps: From 12fb3412a5aca30afaffb8cff3cd90b27fe8f0b2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 12:52:07 +0000 Subject: [PATCH 394/861] remove concurrency groups --- .github/workflows/ci-post-merge.yml | 14 -------------- .github/workflows/ci.yml | 9 --------- 2 files changed, 23 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 8057e4e11..d37b2c107 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -19,10 +19,6 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} - concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} - cancel-in-progress: true - env: CI: 1 CARGO_INCREMENTAL: 0 @@ -99,11 +95,6 @@ jobs: ci_feature_powerset_check: name: Verify Feature Combinations runs-on: ubuntu-latest - - concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} - cancel-in-progress: true - steps: - uses: actions/checkout@v2 @@ -165,11 +156,6 @@ jobs: nextest: name: nextest runs-on: ubuntu-latest - - concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} - cancel-in-progress: true - steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21658cb33..f41aa972f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,6 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} - concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} - cancel-in-progress: true - env: CI: 1 CARGO_INCREMENTAL: 0 @@ -102,11 +98,6 @@ jobs: rustdoc: name: doc tests runs-on: ubuntu-latest - - concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-${{ github.job }}-${{ github.job.name }} - cancel-in-progress: true - steps: - uses: actions/checkout@v2 From d0b5fb18d2f16f42743518363be8b9e0737cee56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 22 Feb 2022 17:40:38 +0000 Subject: [PATCH 395/861] update migration guide on middleware --- actix-web/MIGRATION-4.0.md | 92 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 5bdf7d312..787487e45 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -239,11 +239,97 @@ The following is a migration for an error handler that creates a new response in ## Middleware Trait APIs -This section builds upon guidance from the [response body types](#response-body-types) section. +The underlying traits that are used for creating middleware, `Service`, `ServiceFactory`, and `Transform`, have changed in design. -TODO +- The associated `Request` type has moved to the type parameter position in order to allow multiple request implementations in other areas of the service stack. +- The `self` arguments in `Service` have changed from exclusive (mutable) borrows to shared (immutable) borrows. Since most service layers, such as middleware, do not host mutable state, it reduces the runtime overhead in places where a `RefCell` used to be required for wrapping an inner service. +- We've also introduced some macros that reduce boilerplate when implementing `poll_ready`. +- Further to the guidance on [response body types](#response-body-types), any use of the old methods on `ServiceResponse` designed to match up body types (e.g., the old `into_body` method), should be replaced with an explicit response body type utilizing `EitherBody`. -TODO: Also write the Middleware author's guide. +A typical migration would look like this: + +```diff + use std::{ +- cell::RefCell, + future::Future, + pin::Pin, + rc::Rc, +- task::{Context, Poll}, + }; + + use actix_web::{ + dev::{Service, ServiceRequest, ServiceResponse, Transform}, + Error, + }; + use futures_util::future::{ok, LocalBoxFuture, Ready}; + + pub struct SayHi; + +- impl Transform for SayHi ++ impl Transform for SayHi + where +- S: Service, Error = Error>, ++ S: Service, Error = Error>, + S::Future: 'static, + B: 'static, + { +- type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = SayHiMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(SayHiMiddleware { +- service: Rc::new(RefCell::new(service)), ++ service: Rc::new(service), + }) + } + } + + pub struct SayHiMiddleware { +- service: Rc>, ++ service: Rc, + } + +- impl Service for SayHiMiddleware ++ impl Service for SayHiMiddleware + where +- S: Service, Error = Error>, ++ S: Service, Error = Error>, + S::Future: 'static, + B: 'static, + { +- type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + +- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { +- self.service.poll_ready(cx) +- } ++ actix_web::dev::forward_ready!(service); + +- fn call(&mut self, req: ServiceRequest) -> Self::Future { ++ fn call(&self, req: ServiceRequest) -> Self::Future { + println!("Hi from start. You requested: {}", req.path()); + + let fut = self.service.call(req); + + Box::pin(async move { + let res = fut.await?; + + println!("Hi from response"); + Ok(res) + }) + } + } +``` + +This new design is forward-looking and should ease transition to traits that support the upcoming Generic Associated Type (GAT) feature in Rust while also trimming down the boilerplate required to implement middleware. + +We understand that creating middleware is still a pain point for Actix Web and we hope to provide [an even more ergonomic solution](https://docs.rs/actix-web-lab/0.11.0/actix_web_lab/middleware/fn.from_fn.html) in a v4.x release. ## `Responder` Trait From d0c08dbb7dfd8b2096585a5b613c2be476199e73 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 18:46:35 +0000 Subject: [PATCH 396/861] prepare releases: actix-http 3.0.0 and actix-web 4.0.0 (#2663) --- CHANGES.md | 2 +- actix-files/Cargo.toml | 6 +- actix-http-test/Cargo.toml | 4 +- actix-http/CHANGES.md | 289 ++++++++++++++++++++++++++++++++++- actix-http/Cargo.toml | 4 +- actix-http/README.md | 4 +- actix-multipart/Cargo.toml | 4 +- actix-test/Cargo.toml | 4 +- actix-web-actors/Cargo.toml | 4 +- actix-web-codegen/CHANGES.md | 9 ++ actix-web-codegen/Cargo.toml | 4 +- actix-web-codegen/README.md | 4 +- actix-web/CHANGES.md | 273 ++++++++++++++++++++++++++++++++- actix-web/Cargo.toml | 6 +- actix-web/MIGRATION-4.0.md | 2 +- actix-web/README.md | 4 +- actix-web/src/dev.rs | 4 + actix-web/src/web.rs | 16 +- awc/Cargo.toml | 6 +- 19 files changed, 613 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d95c477fd..7bec65640 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -# Changes +# Changelog Changelogs are kept separately for each crate in this repo. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6e7f7402e..39c5f05c5 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,10 +22,10 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0-rc.4" +actix-http = "3.0.0" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0-rc.3", default-features = false } +actix-web = { version = "4.0.0", default-features = false } askama_escape = "0.10" bitflags = "1" @@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.13" -actix-web = "4.0.0-rc.3" +actix-web = "4.0.0" tempfile = "3.2" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 7d310aef9..e2a2bcc3d 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-rc.4" +actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } +actix-http = "3.0.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 0561e82fc..d73e8522f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,294 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0 - 2022-02-25 +### Dependencies +- Updated `actix-*` to Tokio v1-based versions. [#1813] +- Updated `bytes` to `1.0`. [#1813] +- Updated `h2` to `0.3`. [#1813] +- Updated `rustls` to `0.20.0`. [#2414] +- Updated `language-tags` to `0.3`. +- Updated `tokio` to `1`. + +### Added +- Crate Features: + - `ws`; disabled by default. [#2618] + - `http2`; disabled by default. [#2618] + - `compress-brotli`; disabled by default. [#2618] + - `compress-gzip`; disabled by default. [#2618] + - `compress-zstd`; disabled by default. [#2618] +- Functions: + - `body::to_bytes` for async collecting message body into Bytes. [#2158] +- Traits: + - `TryIntoHeaderPair`; allows using typed and untyped headers in the same methods. [#1869] +- Types: + - `body::BoxBody`; a boxed message body with boxed errors. [#2183] + - `body::EitherBody` enum. [#2468] + - `body::None` struct. [#2468] + - Re-export `http` crate's `Error` type as `error::HttpError`. [#2171] +- Variants: + - `ContentEncoding::Zstd` along with . [#2244] + - `Protocol::Http3` for future compatibility and also mark `#[non_exhaustive]`. [00ba8d55] +- Methods: + - `ContentEncoding::to_header_value()`. [#2501] + - `header::QualityItem::{max, min}()`. [#2486] + - `header::QualityItem::zero()` that uses `Quality::ZERO`. [#2501] + - `HeaderMap::drain()` as an efficient draining iterator. [#1964] + - `HeaderMap::len_keys()` has the behavior of the old `len` method. [#1964] + - `MessageBody::boxed` trait method for wrapping boxing types efficiently. [#2520] + - `MessageBody::try_into_bytes` trait method, with default implementation, for optimizations on body types that complete in exactly one poll. [#2522] + - `Request::conn_data()`. [#2491] + - `Request::take_conn_data()`. [#2491] + - `Request::take_req_data()`. [#2487] + - `Response::{ok, bad_request, not_found, internal_server_error}()`. [#2159] + - `Response::into_body()` that consumes response and returns body type. [#2201] + - `Response::map_into_boxed_body()`. [#2468] + - `ResponseBuilder::append_header()` method which allows using typed and untyped headers. [#1869] + - `ResponseBuilder::insert_header()` method which allows using typed and untyped headers. [#1869] + - `ResponseHead::set_camel_case_headers()`. [#2587] + - `TestRequest::insert_header()` method which allows using typed and untyped headers. [#1869] +- Implementations: + - Implement `Clone for ws::HandshakeError`. [#2468] + - Implement `Clone` for `body::AnyBody where S: Clone`. [#2448] + - Implement `Clone` for `RequestHead`. [#2487] + - Implement `Clone` for `ResponseHead`. [#2585] + - Implement `Copy` for `QualityItem where T: Copy`. [#2501] + - Implement `Default` for `ContentEncoding`. [#1912] + - Implement `Default` for `HttpServiceBuilder`. [#2611] + - Implement `Default` for `KeepAlive`. [#2611] + - Implement `Default` for `Response`. [#2201] + - Implement `Default` for `ws::Codec`. [#1920] + - Implement `Display` for `header::Quality`. [#2486] + - Implement `Eq` for `header::ContentEncoding`. [#2501] + - Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470] + - Implement `From` for `KeepAlive`. [#2611] + - Implement `From>` for `KeepAlive`. [#2611] + - Implement `From>` for `Response>`. [#2625] + - Implement `FromStr` for `ContentEncoding`. [#1912] + - Implement `Header` for `ContentEncoding`. [#1912] + - Implement `IntoHeaderValue` for `ContentEncoding`. [#1912] + - Implement `IntoIterator` for `HeaderMap`. [#1964] + - Implement `MessageBody` for `bytestring::ByteString`. [#2468] + - Implement `MessageBody` for `Pin> where T: MessageBody`. [#2152] +- Misc: + - Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171] + - Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171] + - `Quality::ZERO` associated constant equivalent to `q=0`. [#2501] + - `header::Quality::{MAX, MIN}` associated constants equivalent to `q=1` and `q=0.001`, respectively. [#2486] + - Timeout for canceling HTTP/2 server side connection handshake. Configurable with `ServiceConfig::client_timeout`; defaults to 5 seconds. [#2483] + - `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] + +### Changed +- Traits: + - Rename `IntoHeaderValue => TryIntoHeaderValue`. [#2510] + - `MessageBody` now has an associated `Error` type. [#2183] +- Types: + - `Protocol` enum is now marked `#[non_exhaustive]`. + - `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624] + - `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] + - Error enums are marked `#[non_exhaustive]`. [#2161] + - Rename `PayloadStream` to `BoxedPayloadStream`. [#2545] + - The body type parameter of `Response` no longer has a default. [#2152] +- Enum Variants: + - Rename `ContentEncoding::{Br => Brotli}`. [#2501] + - `Payload` inner fields are now named. [#2545] + - `ws::Message::Text` now contains a `bytestring::ByteString`. [#1864] +- Methods: + - Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] + - Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611] + - Rename `h1::Codec::{keepalive => keep_alive}`. [#2611] + - Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611] + - Rename `h1::ClientCodec::{keepalive => keep_alive}`. [#2611] + - Rename `h1::ClientPayloadCodec::{keepalive => keep_alive}`. [#2611] + - Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565] + - Rename `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] + - Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611] + - Places in `Response` where `ResponseBody` was received or returned now simply use `B`. [#2201] + - `encoding::Encoder::response` now returns `AnyBody>`. [#2448] + - `Extensions::insert` returns replaced item. [#1904] + - `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] + - `HeaderMap::insert` now returns iterator of removed values. [#1964] + - `HeaderMap::len` now returns number of values instead of number of keys. [#1964] + - `HeaderMap::remove` now returns iterator of removed values. [#1964] + - `ResponseBuilder::body(B)` now returns `Response>`. [#2468] + - `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894] + - `ResponseBuilder::finish()` now returns `Response>`. [#2468] + - `ResponseBuilder::json` now takes `impl Serialize`. [#2052] + - `ResponseBuilder::message_body` now returns a `Result`. [#2201]∑ + - `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611] + - `ws::hash_key` now returns array. [#2035] +- Trait Implementations: + - Implementation of `Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545] + - Implementation of `Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545] + - Implementation of `Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545] + - Implementation of `From` for error types now return a `Response`. [#2468] +- Misc: + - `header` module is now public. [#2171] + - `uri` module is now public. [#2171] + - Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487] + - All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] + - All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] + - Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467] + - Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] + - Brotli (de)compression support is now provided by the `brotli` crate. [#2538] + - Minimum supported Rust version (MSRV) is now 1.54. + +### Fixed +- A `Vary` header is now correctly sent along with compressed content. [#2501] +- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611] +- Fixed issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624] +- `ContentEncoding`'s `Identity` variant can now be parsed from a string. [#2501] +- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuration parameter. [#2226] +- Remove unnecessary `Into` bound on `Encoder` body types. [#2375] +- Remove unnecessary `Unpin` bound on `ResponseBuilder::streaming`. [#2253] +- `BodyStream` and `SizedStream` are no longer restricted to `Unpin` types. [#2152] +- Fixed slice creation pointing to potential uninitialized data on h1 encoder. [#2364] +- Fixed quality parse error in Accept-Encoding header. [#2344] + ### Removed -- `error::BlockingError` [#2660] +- Crate Features: + - `compress` feature. [#2065] + - `cookies` feature. [#2065] + - `trust-dns` feature. [#2425] + - `actors` optional feature and trait implementation for `actix` types. [#1969] +- Functions: + - `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] +- Types: + - `body::Body`; replaced with `EitherBody` and `BoxBody`. [#2468] + - `body::ResponseBody`. [#2446] + - `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. Due to the removal of this type from `tokio-openssl` crate. OpenSSL handshake error now returns `ConnectError::SslError`. [#1813] + - `error::Canceled` re-export. [#1994] + - `error::Result` type alias. [#2201] + - `error::BlockingError` [#2660] + - `InternalError` and all the error types it constructed were moved up to `actix-web`. [#2215] + - Typed HTTP headers; they have moved up to `actix-web`. [2094] + - Re-export of `http` crate's `HeaderMap` types in addition to ours. [#2171] +- Enum Variants: + - `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446] + - `ContentEncoding::Auto`. [#2501] + - `EncoderError::Boxed`. [#2446] +- Methods: + - `ContentEncoding::is_compression()`. [#2501] + - `h1::Payload::readany()`. [#2545] + - `HttpMessage::cookie[s]()` trait methods. [#2065] + - `HttpServiceBuilder::new()`; use `default` instead. [#2611] + - `on_connect` (previously deprecated) methods have been removed; use `on_connect_ext`. [#1857] + - `Response::build_from()`. [#2159] + - `Response::error()` [#2205] + - `Response::take_body()` and old `Response::into_body()` method that casted body type. [#2201] + - `Response`'s status code builders. [#2159] + - `ResponseBuilder::{if_true, if_some}()` (previously deprecated). [#2148] + - `ResponseBuilder::{set, set_header}()`; use `ResponseBuilder::insert_header()`. [#1869] + - `ResponseBuilder::extensions[_mut]()`. [#2585] + - `ResponseBuilder::header()`; use `ResponseBuilder::append_header()`. [#1869] + - `ResponseBuilder::json()`. [#2148] + - `ResponseBuilder::json2()`. [#1903] + - `ResponseBuilder::streaming()`. [#2468] + - `ResponseHead::extensions[_mut]()`. [#2585] + - `ServiceConfig::{client_timer, keep_alive_timer}()`. [#2611] + - `TestRequest::with_hdr()`; use `TestRequest::default().insert_header()`. [#1869] + - `TestRequest::with_header()`; use `TestRequest::default().insert_header()`. [#1869] +- Trait implementations: + - Implementation of `Copy` for `ws::Codec`. [#1920] + - Implementation of `From> for KeepAlive`; use `Duration`s instead. [#2611] + - Implementation of `From` for `Body`. [#2148] + - Implementation of `From for KeepAlive`; use `Duration`s instead. [#2611] + - Implementation of `Future` for `Response`. [#2201] + - Implementation of `Future` for `ResponseBuilder`. [#2468] + - Implementation of `Into` for `Response`. [#2215] + - Implementation of `Into` for `ResponseBuilder`. [#2215] + - Implementation of `ResponseError` for `actix_utils::timeout::TimeoutError`. [#2127] + - Implementation of `ResponseError` for `CookieParseError`. [#2065] + - Implementation of `TryFrom` for `header::Quality`. [#2486] +- Misc: + - `http` module; most everything it contained is exported at the crate root. [#2488] + - `cookies` module (re-export). [#2065] + - `client` module. Connector types now live in `awc`. [#2425] + - `error` field from `Response`. [#2205] + - `downcast` and `downcast_get_type_id` macros. [#2291] + - Down-casting for `MessageBody` types; use standard `Any` trait. [#2183] + +[#1813]: https://github.com/actix/actix-web/pull/1813 +[#1845]: https://github.com/actix/actix-web/pull/1845 +[#1857]: https://github.com/actix/actix-web/pull/1857 +[#1864]: https://github.com/actix/actix-web/pull/1864 +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1878]: https://github.com/actix/actix-web/pull/1878 +[#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 +[#1912]: https://github.com/actix/actix-web/pull/1912 +[#1920]: https://github.com/actix/actix-web/pull/1920 +[#1964]: https://github.com/actix/actix-web/pull/1964 +[#1969]: https://github.com/actix/actix-web/pull/1969 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#1994]: https://github.com/actix/actix-web/pull/1994 +[#2035]: https://github.com/actix/actix-web/pull/2035 +[#2052]: https://github.com/actix/actix-web/pull/2052 +[#2065]: https://github.com/actix/actix-web/pull/2065 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2127]: https://github.com/actix/actix-web/pull/2127 +[#2148]: https://github.com/actix/actix-web/pull/2148 +[#2152]: https://github.com/actix/actix-web/pull/2152 +[#2158]: https://github.com/actix/actix-web/pull/2158 +[#2159]: https://github.com/actix/actix-web/pull/2159 +[#2161]: https://github.com/actix/actix-web/pull/2161 +[#2171]: https://github.com/actix/actix-web/pull/2171 +[#2183]: https://github.com/actix/actix-web/pull/2183 +[#2196]: https://github.com/actix/actix-web/pull/2196 +[#2201]: https://github.com/actix/actix-web/pull/2201 +[#2205]: https://github.com/actix/actix-web/pull/2205 +[#2215]: https://github.com/actix/actix-web/pull/2215 +[#2244]: https://github.com/actix/actix-web/pull/2244 +[#2250]: https://github.com/actix/actix-web/pull/2250 +[#2253]: https://github.com/actix/actix-web/pull/2253 +[#2291]: https://github.com/actix/actix-web/pull/2291 +[#2344]: https://github.com/actix/actix-web/pull/2344 +[#2364]: https://github.com/actix/actix-web/pull/2364 +[#2375]: https://github.com/actix/actix-web/pull/2375 +[#2377]: https://github.com/actix/actix-web/pull/2377 +[#2414]: https://github.com/actix/actix-web/pull/2414 +[#2425]: https://github.com/actix/actix-web/pull/2425 +[#2442]: https://github.com/actix/actix-web/pull/2442 +[#2446]: https://github.com/actix/actix-web/pull/2446 +[#2448]: https://github.com/actix/actix-web/pull/2448 +[#2456]: https://github.com/actix/actix-web/pull/2456 +[#2467]: https://github.com/actix/actix-web/pull/2467 +[#2468]: https://github.com/actix/actix-web/pull/2468 +[#2470]: https://github.com/actix/actix-web/pull/2470 +[#2474]: https://github.com/actix/actix-web/pull/2474 +[#2483]: https://github.com/actix/actix-web/pull/2483 +[#2486]: https://github.com/actix/actix-web/pull/2486 +[#2487]: https://github.com/actix/actix-web/pull/2487 +[#2488]: https://github.com/actix/actix-web/pull/2488 +[#2491]: https://github.com/actix/actix-web/pull/2491 +[#2497]: https://github.com/actix/actix-web/pull/2497 +[#2501]: https://github.com/actix/actix-web/pull/2501 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2520]: https://github.com/actix/actix-web/pull/2520 +[#2522]: https://github.com/actix/actix-web/pull/2522 +[#2527]: https://github.com/actix/actix-web/pull/2527 +[#2538]: https://github.com/actix/actix-web/pull/2538 +[#2545]: https://github.com/actix/actix-web/pull/2545 +[#2565]: https://github.com/actix/actix-web/pull/2565 +[#2585]: https://github.com/actix/actix-web/pull/2585 +[#2587]: https://github.com/actix/actix-web/pull/2587 +[#2611]: https://github.com/actix/actix-web/pull/2611 +[#2618]: https://github.com/actix/actix-web/pull/2618 +[#2624]: https://github.com/actix/actix-web/pull/2624 +[#2625]: https://github.com/actix/actix-web/pull/2625 [#2660]: https://github.com/actix/actix-web/pull/2660 +[00ba8d55]: https://github.com/actix/actix-web/commit/00ba8d55492284581695d824648590715a8bd386 +
+3.0.0 Pre-Releases + ## 3.0.0-rc.4 - 2022-02-22 +### Fixed - Fix h1 dispatcher panic. [1ce58ecb] [1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca @@ -108,7 +389,7 @@ ## 3.0.0-beta.17 - 2021-12-27 -### Changes +### Changed - `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527] - `Payload` inner fields are now named. [#2545] - `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545] @@ -331,7 +612,7 @@ - `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159] - Helper `body::to_bytes` for async collecting message body into Bytes. [#2158] -### Changes +### Changed - The type parameter of `Response` no longer has a default. [#2152] - The `Message` variant of `body::Body` is now `Pin>`. [#2152] - `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] @@ -475,6 +756,8 @@ [#1864]: https://github.com/actix/actix-web/pull/1864 [#1878]: https://github.com/actix/actix-web/pull/1878 +
+ ## 2.2.2 - 2022-01-21 ### Changed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b661e2512..751b820e8 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-rc.4" +version = "3.0.0" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -100,7 +100,7 @@ zstd = { version = "0.10", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = "4.0.0-rc.3" +actix-web = "4.0.0" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http/README.md b/actix-http/README.md index 137b94f3a..8d414a0fc 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.4)](https://docs.rs/actix-http/3.0.0-rc.4) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0)](https://docs.rs/actix-http/3.0.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.4) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0/status.svg)](https://deps.rs/crate/actix-http/3.0.0) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index fe7320af2..89d0d370a 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.3", default-features = false } +actix-web = { version = "4.0.0", default-features = false } bytes = "1" derive_more = "0.99.5" @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0-rc.4" +actix-http = "3.0.0" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 19b67cc7f..af4aff56a 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,12 +29,12 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" -actix-http = "3.0.0-rc.4" +actix-http = "3.0.0" actix-http-test = "3.0.0-beta.13" actix-rt = "2.1" actix-service = "2.0.0" actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] } +actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 251a03f02..4c0e700c7 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,8 +16,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.12.0", default-features = false } actix-codec = "0.5" -actix-http = "3.0.0-rc.4" -actix-web = { version = "4.0.0-rc.3", default-features = false } +actix-http = "3.0.0" +actix-web = { version = "4.0.0", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 9483f1b35..8ee787c0a 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -3,6 +3,15 @@ ## Unreleased - 2021-xx-xx +## 4.0.0 - 2022-02-24 +- Version aligned with `actix-web` and will remain in sync going forward. +- No significant changes since `0.5.0`. + + +## 0.5.0 - 2022-02-24 +- No significant changes since `0.5.0-rc.2`. + + ## 0.5.0-rc.2 - 2022-02-01 - No significant changes since `0.5.0-rc.1`. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 71ec3148f..0d8b86459 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.5.0-rc.2" +version = "4.0.0" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" @@ -25,7 +25,7 @@ actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1.0-beta.13" actix-utils = "3.0.0" -actix-web = "4.0.0-rc.3" +actix-web = "4.0.0" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index f4e8344f3..439beadb4 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-rc.2)](https://docs.rs/actix-web-codegen/0.5.0-rc.2) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.0)](https://docs.rs/actix-web-codegen/4.0.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.2/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.2) +[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.0) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index ff4823149..b9d56b67d 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,12 +1,280 @@ -# Changes +# Changelog ## Unreleased - 2021-xx-xx + + +## 4.0.0 - 2022-02-25 +### Dependencies +- Updated `actix-*` to Tokio v1-based versions. [#1813] +- Updated `actix-web-codegen` to `4.0.0`. +- Updated `cookie` to `0.16`. [#2555] +- Updated `language-tags` to `0.3`. +- Updated `rand` to `0.8`. +- Updated `rustls` to `0.20.0`. [#2414] +- Updated `tokio` to `1`. + +### Added +- Crate Features: + - `cookies`; enabled by default. [#2619] + - `compress-brotli`; enabled by default. [#2618] + - `compress-gzip`; enabled by default. [#2618] + - `compress-zstd`; enabled by default. [#2618] + - `macros`; enables routing and runtime macros, enabled by default. [#2619] +- Types: + - `CustomizeResponder` for customizing response. [#2510] + - `dev::ServerHandle` re-export from `actix-server`. [#2442] + - `dev::ServiceFactory` re-export from `actix-service`. [#2325] + - `guard::GuardContext` for use with the `Guard` trait. [#2552] + - `http::header::AcceptEncoding` typed header. [#2482] + - `http::header::Range` typed header. [#2485] + - `http::KeepAlive` re-export from `actix-http`. [#2625] + - `middleware::Compat` that boxes middleware types like `Logger` and `Compress` to be used with constrained type bounds. [#1865] + - `web::Header` extractor for extracting typed HTTP headers in handlers. [#2094] +- Methods: + - `dev::ServiceRequest::guard_ctx()` for obtaining a guard context. [#2552] + - `dev::ServiceRequest::parts_mut()`. [#2177] + - `dev::ServiceResponse::map_into_{left,right}_body()` and `HttpResponse::map_into_boxed_body()`. [#2468] + - `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] + - `http::header::AcceptLanguage::{ranked, preference}()`. [#2480] + - `HttpResponse::add_removal_cookie()`. [#2586] + - `HttpResponse::map_into_{left,right}_body()` and `HttpResponse::map_into_boxed_body()`. [#2468] + - `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] + - `middleware::Logger::log_target()` to allow customize. [#2594] + - `Responder::customize()` trait method that wraps responder in `CustomizeResponder`. [#2510] + - `Route::service()` for using hand-written services as handlers. [#2262] + - `ServiceResponse::into_parts()`. [#2499] + - `TestServer::client_headers()` method. [#2097] + - `web::ServiceConfig::configure()` to allow easy nesting of configuration functions. [#1988] +- Trait Implementations: + - Implement `Debug` for `DefaultHeaders`. [#2510] + - Implement `FromRequest` for `ConnectionInfo` and `PeerAddr`. [#2263] + - Implement `FromRequest` for `Method`. [#2263] + - Implement `FromRequest` for `Uri`. [#2263] + - Implement `Hash` for `http::header::Encoding`. [#2501] + - Implement `Responder` for `Vec`. [#2625] +- Misc: + - `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] + - Enable registering a vec of services of the same type to `App` [#1933] + - Add `services!` macro for helping register multiple services to `App`. [#1933] + - Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362] + - Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] + ### Changed -- Rename `test::{simple_service => status_service}`. [#2659] +- Functions: + - `guard::fn_guard` functions now receives a `&GuardContext`. [#2552] + - `guard::Not` is now generic over the type of guard it wraps. [#2552] + - `test::{call_service, read_response, read_response_json, send_request}()` now receive a `&Service`. [#1905] + - Some guard functions now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552] + - Rename `test::{default_service => status_service}()`. Old name is deprecated. [#2518] + - Rename `test::{read_response_json => call_and_read_body_json}()`. Old name is deprecated. [#2518] + - Rename `test::{read_response => call_and_read_body}()`. Old name is deprecated. [#2518] +- Traits: + - `guard::Guard::check` now receives a `&GuardContext`. [#2552] + - `FromRequest::Config` associated type was removed. [#2233] + - `Responder` trait has been reworked and now `Response`/`HttpResponse` synchronously, making it simpler and more performant. [#1891] + - Rename `Factory` trait to `Handler`. [#1852] +- Types: + - `App`'s `B` (body) type parameter been removed. As a result, `App`s can be returned from functions now. [#2493] + - `Compress` middleware's response type is now `EitherBody>`. [#2448] + - `error::BlockingError` is now a unit struct. It's now only triggered when blocking thread pool has shutdown. [#1957] + - `ErrorHandlerResponse`'s response variants now use `ServiceResponse>`. [#2515] + - `ErrorHandlers` middleware's response types now use `ServiceResponse>`. [#2515] + - `http::header::Encoding` now only represents `Content-Encoding` types. [#2501] + - `middleware::Condition` gained a broader middleware compatibility. [#2635] + - `Resource` no longer require service body type to be boxed. [#2526] + - `Scope` no longer require service body type to be boxed. [#2523] + - `web::Path`s inner field is now private. [#1894] + - `web::Payload`'s inner field is now private. [#2384] + - Error enums are now marked `#[non_exhaustive]`. [#2148] +- Enum Variants: + - `Either` now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] + - Include size and limits in `JsonPayloadError::Overflow`. [#2162] +- Methods: + - `App::data()` is deprecated; `App::app_data()` should be preferred. [#2271] + - `dev::JsonBody::new()` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json` extractor. [#2010] + - `dev::ServiceRequest::{into_parts, from_parts}()` can no longer fail. [#1893] + - `dev::ServiceRequest::from_request` can no longer fail. [#1893] + - `dev::ServiceResponse::error_response()` now uses body type of `BoxBody`. [#2201] + - `dev::ServiceResponse::map_body()` closure receives and returns `B` instead of `ResponseBody`. [#2201] + - `http::header::ContentType::html()` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423] + - `HttpRequest::url_for`'s constructed URLs no longer contain query or fragment. [#2430] + - `HttpResponseBuilder::json()` can now receive data by value and reference. [#1903] + - `HttpServer::{listen_rustls, bind_rustls}()` now honor the ALPN protocols in the configuration parameter. [#2226] + - `middleware::NormalizePath()` now will not try to normalize URIs with no valid path [#2246] + - `test::TestRequest::param()` now accepts more than just static strings. [#2172] + - `web::Data::into_inner()` and `Data::get_ref()` no longer require `T: Sized`. [#2403] + - Rename `HttpServer::{client_timeout => client_request_timeout}()`. [#2611] + - Rename `HttpServer::{client_shutdown => client_disconnect_timeout}()`. [#2611] + - Rename `http::header::Accept::{mime_precedence => ranked}()`. [#2480] + - Rename `http::header::Accept::{mime_preference => preference}()`. [#2480] + - Rename `middleware::DefaultHeaders::{content_type => add_content_type}()`. [#1875] + - Rename `dev::ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554] +- Trait Implementations: + - `HttpResponse` can now be used as a `Responder` with any body type. [#2567] +- Misc: + - Maximum number of handler extractors has increased to 12. [#2582] + - The default `TrailingSlash` behavior is now `Trim`, in line with existing documentation. See migration guide for implications. [#1875] + - `Result` extractor wrapper can now convert error types. [#2581] + - Compress middleware will return `406 Not Acceptable` when no content encoding is acceptable to the client. [#2344] + - Adjusted default JSON payload limit to 2MB (from 32kb). [#2162] + - All error trait bounds in server service builders have changed from `Into` to `Into>`. [#2253] + - All error trait bounds in message body and stream impls changed from `Into` to `Into>`. [#2253] + - Improve spec compliance of `dev::ConnectionInfo` extractor. [#2282] + - Associated types in `FromRequest` implementation for `Option` and `Result` have changed. [#2581] + - Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] + - Minimum supported Rust version (MSRV) is now 1.54. +### Fixed +- Auto-negotiation of content encoding is more fault-tolerant when using the `Compress` middleware. [#2501] +- Scope and Resource middleware can access data items set on their own layer. [#2288] +- Multiple calls to `App::data()` with the same type now keeps the latest call's data. [#1906] +- Typed headers containing lists that require one or more items now enforce this minimum. [#2482] +- `dev::ConnectionInfo::peer_addr` will no longer return the port number. [#2554] +- `dev::ConnectionInfo::realip_remote_addr` will no longer return the port number if sourcing the IP from the peer's socket address. [#2554] +- Accept wildcard `*` items in `AcceptLanguage`. [#2480] +- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448] +- Fix quality parse error in `http::header::AcceptEncoding` typed header. [#2344] +- Double ampersand in `middleware::Logger` format is escaped correctly. [#2067] +- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[`rustsec-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + +### Removed +- Crate Features: + - `compress` feature. [#2065] +- Functions: + - `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518] + - `test::start_with`; moved to new `actix-test` crate. [#2112] + - `test::start`; moved to new `actix-test` crate. [#2112] + - `test::unused_addr`; moved to new `actix-test` crate. [#2112] +- Traits: + - `BodyEncoding`; signalling content encoding is now only done via the `Content-Encoding` header. [#2565] +- Types: + - `dev::{BodySize, MessageBody, SizedStream}` re-exports; they are exposed through the `body` module. [#2468] + - `EitherExtractError` direct export. [#2510] + - `rt::{Arbiter, ArbiterHandle}` re-exports. [#2619] + - `test::TestServer`; moved to new `actix-test` crate. [#2112] + - `test::TestServerConfig`; moved to new `actix-test` crate. [#2112] + - `web::HttpRequest` re-export. [#2663] + - `web::HttpResponse` re-export. [#2663] +- Methods: + - `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] + - `dev::ConnectionInfo::get`. [#2487] + - `dev::ServiceResponse::checked_expr`. [#2401] + - `HttpRequestBuilder::del_cookie`. [#2591] + - `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] + - `HttpResponseBuilder::json2()`. [#1903] + - `middleware::Compress::new`; restricting compression algorithm is done through feature flags. [#2501] + - `test::TestRequest::with_header()`; use `test::TestRequest::default().insert_header()`. [#1869] +- Trait Implementations: + - Implementation of `From` for `Either` crate. [#2516] + - Implementation of `Future` for `HttpResponse`. [#2601] +- Misc: + - The `client` module was removed; use the `awc` crate directly. [871ca5e4] + - `middleware::{normalize, err_handlers}` modules; all necessary middleware types are now exposed in the `middleware` module. + +[#1812]: https://github.com/actix/actix-web/pull/1812 +[#1813]: https://github.com/actix/actix-web/pull/1813 +[#1852]: https://github.com/actix/actix-web/pull/1852 +[#1865]: https://github.com/actix/actix-web/pull/1865 +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1875]: https://github.com/actix/actix-web/pull/1875 +[#1878]: https://github.com/actix/actix-web/pull/1878 +[#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 +[#1903]: https://github.com/actix/actix-web/pull/1903 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1906]: https://github.com/actix/actix-web/pull/1906 +[#1933]: https://github.com/actix/actix-web/pull/1933 +[#1957]: https://github.com/actix/actix-web/pull/1957 +[#1957]: https://github.com/actix/actix-web/pull/1957 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#1988]: https://github.com/actix/actix-web/pull/1988 +[#2010]: https://github.com/actix/actix-web/pull/2010 +[#2065]: https://github.com/actix/actix-web/pull/2065 +[#2067]: https://github.com/actix/actix-web/pull/2067 +[#2093]: https://github.com/actix/actix-web/pull/2093 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2097]: https://github.com/actix/actix-web/pull/2097 +[#2112]: https://github.com/actix/actix-web/pull/2112 +[#2148]: https://github.com/actix/actix-web/pull/2148 +[#2162]: https://github.com/actix/actix-web/pull/2162 +[#2172]: https://github.com/actix/actix-web/pull/2172 +[#2177]: https://github.com/actix/actix-web/pull/2177 +[#2200]: https://github.com/actix/actix-web/pull/2200 +[#2201]: https://github.com/actix/actix-web/pull/2201 +[#2201]: https://github.com/actix/actix-web/pull/2201 +[#2233]: https://github.com/actix/actix-web/pull/2233 +[#2246]: https://github.com/actix/actix-web/pull/2246 +[#2250]: https://github.com/actix/actix-web/pull/2250 +[#2253]: https://github.com/actix/actix-web/pull/2253 +[#2262]: https://github.com/actix/actix-web/pull/2262 +[#2263]: https://github.com/actix/actix-web/pull/2263 +[#2271]: https://github.com/actix/actix-web/pull/2271 +[#2282]: https://github.com/actix/actix-web/pull/2282 +[#2288]: https://github.com/actix/actix-web/pull/2288 +[#2325]: https://github.com/actix/actix-web/pull/2325 +[#2344]: https://github.com/actix/actix-web/pull/2344 +[#2362]: https://github.com/actix/actix-web/pull/2362 +[#2379]: https://github.com/actix/actix-web/pull/2379 +[#2384]: https://github.com/actix/actix-web/pull/2384 +[#2401]: https://github.com/actix/actix-web/pull/2401 +[#2403]: https://github.com/actix/actix-web/pull/2403 +[#2409]: https://github.com/actix/actix-web/pull/2409 +[#2414]: https://github.com/actix/actix-web/pull/2414 +[#2423]: https://github.com/actix/actix-web/pull/2423 +[#2430]: https://github.com/actix/actix-web/pull/2430 +[#2442]: https://github.com/actix/actix-web/pull/2442 +[#2446]: https://github.com/actix/actix-web/pull/2446 +[#2448]: https://github.com/actix/actix-web/pull/2448 +[#2468]: https://github.com/actix/actix-web/pull/2468 +[#2474]: https://github.com/actix/actix-web/pull/2474 +[#2480]: https://github.com/actix/actix-web/pull/2480 +[#2482]: https://github.com/actix/actix-web/pull/2482 +[#2484]: https://github.com/actix/actix-web/pull/2484 +[#2485]: https://github.com/actix/actix-web/pull/2485 +[#2487]: https://github.com/actix/actix-web/pull/2487 +[#2491]: https://github.com/actix/actix-web/pull/2491 +[#2492]: https://github.com/actix/actix-web/pull/2492 +[#2493]: https://github.com/actix/actix-web/pull/2493 +[#2499]: https://github.com/actix/actix-web/pull/2499 +[#2501]: https://github.com/actix/actix-web/pull/2501 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2515]: https://github.com/actix/actix-web/pull/2515 +[#2516]: https://github.com/actix/actix-web/pull/2516 +[#2518]: https://github.com/actix/actix-web/pull/2518 +[#2523]: https://github.com/actix/actix-web/pull/2523 +[#2526]: https://github.com/actix/actix-web/pull/2526 +[#2552]: https://github.com/actix/actix-web/pull/2552 +[#2554]: https://github.com/actix/actix-web/pull/2554 +[#2555]: https://github.com/actix/actix-web/pull/2555 +[#2565]: https://github.com/actix/actix-web/pull/2565 +[#2567]: https://github.com/actix/actix-web/pull/2567 +[#2569]: https://github.com/actix/actix-web/pull/2569 +[#2581]: https://github.com/actix/actix-web/pull/2581 +[#2582]: https://github.com/actix/actix-web/pull/2582 +[#2584]: https://github.com/actix/actix-web/pull/2584 +[#2585]: https://github.com/actix/actix-web/pull/2585 +[#2586]: https://github.com/actix/actix-web/pull/2586 +[#2591]: https://github.com/actix/actix-web/pull/2591 +[#2594]: https://github.com/actix/actix-web/pull/2594 +[#2601]: https://github.com/actix/actix-web/pull/2601 +[#2611]: https://github.com/actix/actix-web/pull/2611 +[#2619]: https://github.com/actix/actix-web/pull/2619 +[#2625]: https://github.com/actix/actix-web/pull/2625 +[#2635]: https://github.com/actix/actix-web/pull/2635 [#2659]: https://github.com/actix/actix-web/pull/2659 +[#2663]: https://github.com/actix/actix-web/pull/2663 +[871ca5e4]: https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7 +
+4.0.0 Pre-Releases + ## 4.0.0-rc.3 - 2022-02-08 ### Changed - `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635] @@ -453,6 +721,7 @@ [#1875]: https://github.com/actix/actix-web/pull/1875 [#1878]: https://github.com/actix/actix-web/pull/1878 +
## 3.3.3 - 2021-12-18 ### Changed diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 33c897dc7..43f69cf46 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-rc.3" +version = "4.0.0" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -71,9 +71,9 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] } +actix-http = { version = "3.0.0", features = ["http2", "ws"] } actix-router = "0.5.0" -actix-web-codegen = { version = "0.5.0-rc.2", optional = true } +actix-web-codegen = { version = "4.0.0", optional = true } ahash = "0.7" bytes = "1" diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 787487e45..6ba6717a6 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -3,7 +3,7 @@ This guide walks you through the process of migrating from v3.x.y to v4.x.y. If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder. -This document is not designed to be exhaustive—it focuses on the most significant changes coming in v4. You can find an exhaustive changelog in [CHANGES.md](./CHANGES.md), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. +This document is not designed to be exhaustive—it focuses on the most significant changes coming in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. diff --git a/actix-web/README.md b/actix-web/README.md index e66224524..b8cf334b4 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.3)](https://docs.rs/actix-web/4.0.0-rc.3) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0)](https://docs.rs/actix-web/4.0.0) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.3/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.3) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0/status.svg)](https://deps.rs/crate/actix-web/4.0.0)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-web/src/dev.rs b/actix-web/src/dev.rs index def545ec7..5c7adfdaf 100644 --- a/actix-web/src/dev.rs +++ b/actix-web/src/dev.rs @@ -2,6 +2,10 @@ //! //! Most users will not have to interact with the types in this module, but it is useful for those //! writing extractors, middleware, libraries, or interacting with the service API directly. +//! +//! # Request Extractors +//! - [`ConnectionInfo`]: Connection information +//! - [`PeerAddr`]: Connection information pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; diff --git a/actix-web/src/web.rs b/actix-web/src/web.rs index 4858600af..f5845d7f6 100644 --- a/actix-web/src/web.rs +++ b/actix-web/src/web.rs @@ -1,4 +1,18 @@ //! Essentials helper functions and types for application registration. +//! +//! # Request Extractors +//! - [`Data`]: Application data item +//! - [`ReqData`]: Request-local data item +//! - [`Path`]: URL path parameters / dynamic segments +//! - [`Query`]: URL query parameters +//! - [`Header`]: Typed header +//! - [`Json`]: JSON payload +//! - [`Form`]: URL-encoded payload +//! - [`Bytes`]: Raw payload +//! +//! # Responders +//! - [`Json`]: JSON request payload +//! - [`Bytes`]: Raw request payload use std::future::Future; @@ -12,9 +26,7 @@ use crate::{ pub use crate::config::ServiceConfig; pub use crate::data::Data; -pub use crate::request::HttpRequest; pub use crate::request_data::ReqData; -pub use crate::response::HttpResponse; pub use crate::types::*; /// Creates a new resource for a specific path. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c8e1cbc60..40d9d34b6 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" actix-service = "2.0.0" -actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] } +actix-http = { version = "3.0.0", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } actix-utils = "3.0.0" @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0-rc.4", features = ["openssl"] } +actix-http = { version = "3.0.0", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-utils = "3.0.0" -actix-web = { version = "4.0.0-rc.3", features = ["openssl"] } +actix-web = { version = "4.0.0", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" From 542200cbc28bb46ea0949852734da0dfd35eaebb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 19:11:46 +0000 Subject: [PATCH 397/861] update readme --- actix-web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/README.md b/actix-web/README.md index b8cf334b4..ec7752de8 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -48,7 +48,7 @@ Dependencies: ```toml [dependencies] -actix-web = "4.0.0-rc.1" +actix-web = "4.0.0" ``` Code: From d4a5d450de7811e391d19593b871b5b6f614df8f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 20:31:46 +0000 Subject: [PATCH 398/861] prepare actix-web release 4.0.1 --- actix-web/CHANGES.md | 4 ++++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index b9d56b67d..83c924316 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.1 - 2022-02-25 +- No significant changes since `4.0.0`. + + ## 4.0.0 - 2022-02-25 ### Dependencies - Updated `actix-*` to Tokio v1-based versions. [#1813] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 43f69cf46..52f89747c 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0" +version = "4.0.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index ec7752de8..fcc09c87e 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0)](https://docs.rs/actix-web/4.0.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0/status.svg)](https://deps.rs/crate/actix-web/4.0.0) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From cb379c0e0c41bdf1e5eeff500c6af6b0c790bc7e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 20:36:16 +0000 Subject: [PATCH 399/861] prepare actix-files release 0.6.0 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 6 +++--- actix-files/README.md | 4 ++-- actix-web/Cargo.toml | 2 +- actix-web/README.md | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index f0234b561..3f8e2a823 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0 - 2022-02-25 +- No significant changes since `0.6.0-beta.16`. + + ## 0.6.0-beta.16 - 2022-01-31 - No significant changes since `0.6.0-beta.15`. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 39c5f05c5..e7e6aea23 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.16" +version = "0.6.0" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -22,10 +22,10 @@ path = "src/lib.rs" experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] [dependencies] -actix-http = "3.0.0" +actix-http = "3" actix-service = "2" actix-utils = "3" -actix-web = { version = "4.0.0", default-features = false } +actix-web = { version = "4", default-features = false } askama_escape = "0.10" bitflags = "1" diff --git a/actix-files/README.md b/actix-files/README.md index 8ac80860e..3c4d4443c 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.16)](https://docs.rs/actix-files/0.6.0-beta.16) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.16/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.16) +[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 52f89747c..6e453026a 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -100,7 +100,7 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0-beta.16" +actix-files = "0.6.0" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.21", features = ["openssl"] } diff --git a/actix-web/README.md b/actix-web/README.md index fcc09c87e..d0abb3aae 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -48,7 +48,7 @@ Dependencies: ```toml [dependencies] -actix-web = "4.0.0" +actix-web = "4" ``` Code: From 075932d82307070972c44680ad3f72449dda8371 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 20:41:33 +0000 Subject: [PATCH 400/861] prepare actix-web-actors release 4.0.0 --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 2 +- actix-web-actors/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 124fe23b1..07ca6a130 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0 - 2022-02-25 +- No significant changes since `4.0.0-beta.12`. + + ## 4.0.0-beta.12 - 2022-02-16 - No significant changes since `4.0.0-beta.11`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 4c0e700c7..7cc53d63d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.12" +version = "4.0.0" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 0964cb04e..473a78ad9 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web-actors/4.0.0-beta.12) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0)](https://docs.rs/actix-web-actors/4.0.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From fcca515387df8f014b3b5ea89a7666643504d41d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 20:41:57 +0000 Subject: [PATCH 401/861] prepare actix-multipart release 0.4.0 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 71c8e958f..11ec8a64f 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0 - 2022-02-25 +- No significant changes since `0.4.0-beta.13`. + + ## 0.4.0-beta.13 - 2022-01-31 - No significant changes since `0.4.0-beta.12`. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 89d0d370a..450a57fa9 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.13" +version = "0.4.0" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-multipart/README.md b/actix-multipart/README.md index b517e8ded..59b9651f1 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.13)](https://docs.rs/actix-multipart/0.4.0-beta.13) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.13/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.13) +[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 9f964751f629936d447c27e5dca64fe1c96f4a83 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Feb 2022 21:40:23 +0000 Subject: [PATCH 402/861] tweak migration doc --- actix-web/CHANGES.md | 3 ++- actix-web/MIGRATION-4.0.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 83c924316..bf5caee86 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,7 +4,8 @@ ## 4.0.1 - 2022-02-25 -- No significant changes since `4.0.0`. +### Fixed +- Use stable version in readme example. ## 4.0.0 - 2022-02-25 diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 6ba6717a6..5127c245b 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -3,7 +3,7 @@ This guide walks you through the process of migrating from v3.x.y to v4.x.y. If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder. -This document is not designed to be exhaustive—it focuses on the most significant changes coming in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. +This document is not designed to be exhaustive—it focuses on the most significant changes in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete with PR links. If you think there are any changes that deserve to be called out in this document, please open an issue or pull request. Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. From 2f13e5f67579238761aba34e35786026ce4c805c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Feb 2022 17:13:42 +0000 Subject: [PATCH 403/861] Update MIGRATION-4.0.md --- actix-web/MIGRATION-4.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 5127c245b..e5f597f3c 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -111,6 +111,8 @@ The inner field for `web::Path` is now private. It was causing ambiguity when tr + let (foo, bar) = params.into_inner(); ``` +An alternative [path param type with public field but no `Deref` impl is available in `actix-web-lab`](https://docs.rs/actix-web-lab/0.12.0/actix_web_lab/extract/struct.Path.html). + ## Rustls Crate Upgrade Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/) From e7a05f98925dcd8461845b67fa5769b24aa88961 Mon Sep 17 00:00:00 2001 From: Daze Date: Tue, 1 Mar 2022 05:47:08 +0545 Subject: [PATCH 404/861] fix(docs): TestRequest example fixed (#2643) Co-authored-by: Rob Ede --- actix-web/src/test/test_request.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index fc42253d7..a368d873f 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -24,10 +24,10 @@ use crate::cookie::{Cookie, CookieJar}; /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// You can generate various types of request via TestRequest's methods: -/// * `TestRequest::to_request` creates `actix_http::Request` instance. -/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. -/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. -/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. +/// - [`TestRequest::to_request`] creates an [`actix_http::Request`](Request). +/// - [`TestRequest::to_srv_request`] creates a [`ServiceRequest`], which is used for testing middlewares and chain adapters. +/// - [`TestRequest::to_srv_response`] creates a [`ServiceResponse`]. +/// - [`TestRequest::to_http_request`] creates an [`HttpRequest`], which is used for testing handlers. /// /// ``` /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; @@ -42,15 +42,17 @@ use crate::cookie::{Cookie, CookieJar}; /// } /// /// #[actix_web::test] +/// # // force rustdoc to display the correct thing and also compile check the test +/// # async fn _test() {} /// async fn test_index() { -/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") +/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext()) /// .to_http_request(); /// -/// let resp = index(req).await.unwrap(); +/// let resp = index(req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await.unwrap(); +/// let resp = index(req).await; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From 25c067327890b9095962b992cee60455ab47e13f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 02:20:48 +0000 Subject: [PATCH 405/861] Update MIGRATION-4.0.md --- actix-web/MIGRATION-4.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e5f597f3c..7192d0bc6 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -29,7 +29,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr - [Server Must Be Polled :warning:](#server-must-be-polled-warning) - [Guards API](#guards-api) - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) -- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain) +- [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain) - [`web::block`](#webblock) ## MSRV From 3f03af1c5928e1c4ea0cfab4d2ddfc7043b571f1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 03:25:30 +0000 Subject: [PATCH 406/861] clippy --- actix-web/src/info.rs | 2 +- actix-web/src/rmap.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs index ce1ef97c6..77b98110e 100644 --- a/actix-web/src/info.rs +++ b/actix-web/src/info.rs @@ -159,7 +159,7 @@ impl ConnectionInfo { pub fn realip_remote_addr(&self) -> Option<&str> { self.realip_remote_addr .as_deref() - .or_else(|| self.peer_addr.as_deref()) + .or(self.peer_addr.as_deref()) } /// Returns serialized IP address of the peer connection. diff --git a/actix-web/src/rmap.rs b/actix-web/src/rmap.rs index 932f7acde..6a1a187b2 100644 --- a/actix-web/src/rmap.rs +++ b/actix-web/src/rmap.rs @@ -151,7 +151,7 @@ impl ResourceMap { .char_indices() .filter_map(|(i, c)| (c == '/').then(|| i)) .nth(2) - .unwrap_or_else(|| path.len()); + .unwrap_or(path.len()); ( Cow::Borrowed(&path[..third_slash_index]), From 56e5c19b85d7bb58cf619b2145dff058d950f3ca Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 17:53:47 +0000 Subject: [PATCH 407/861] add actix 0.13 support (#2675) --- actix-web-actors/CHANGES.md | 6 ++++++ actix-web-actors/Cargo.toml | 10 +++++----- actix-web-actors/README.md | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 07ca6a130..b4844bfa6 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 4.1.0 - 2022-03-02 +- Add support for `actix` version `0.13`. [#2675] + +[#2675]: https://github.com/actix/actix-web/pull/2675 + + ## 4.0.0 - 2022-02-25 - No significant changes since `4.0.0-beta.12`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7cc53d63d..225326565 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0" +version = "4.1.0" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] @@ -14,16 +14,16 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = { version = "0.12.0", default-features = false } +actix = { version = ">=0.12, <0.14", default-features = false } actix-codec = "0.5" -actix-http = "3.0.0" -actix-web = { version = "4.0.0", default-features = false } +actix-http = "3" +actix-web = { version = "4", default-features = false } bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" -tokio = { version = "1.8.4", features = ["sync"] } +tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 473a78ad9..357154a86 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0)](https://docs.rs/actix-web-actors/4.0.0) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 955c3ac0c4124f3807d0ac7be647668ea831cecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nerma?= Date: Thu, 3 Mar 2022 01:29:59 +0100 Subject: [PATCH 408/861] Add support for audio files streaming (#2645) --- actix-files/CHANGES.md | 3 +++ actix-files/src/named.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 3f8e2a823..4d4c790e8 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645] + +[#2645]: https://github.com/actix/actix-web/pull/2645 ## 0.6.0 - 2022-02-25 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a307c6385..6f3c6e1c8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -128,7 +128,7 @@ impl NamedFile { let ct = from_path(&path).first_or_octet_stream(); let disposition = match ct.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline, mime::APPLICATION => match ct.subtype() { mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, name if name == "wasm" => DispositionType::Inline, From 49cd303c3b6dfa8e8575c8161e94fd045852ef1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Mar 2022 03:12:38 +0000 Subject: [PATCH 409/861] fix dispatcher panic when conbining pipelining and keepalive fixes #2678 --- actix-http/src/h1/dispatcher.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f029fb108..648cf14d7 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -375,8 +375,6 @@ where DispatchError::Io(err) })?; - this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); - Ok(size) } @@ -459,7 +457,12 @@ where } // all messages are dealt with - None => return Ok(PollResponse::DoNothing), + None => { + // start keep-alive if last request allowed it + this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); + + return Ok(PollResponse::DoNothing); + } }, StateProj::ServiceCall { fut } => { @@ -757,6 +760,7 @@ where let mut updated = false; + // decode from read buf as many full requests as possible loop { match this.codec.decode(this.read_buf) { Ok(Some(msg)) => { From da4c849f6221be0c3a551da6a4a7570ef693b0f3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Mar 2022 03:16:02 +0000 Subject: [PATCH 410/861] prepare actix-http release 3.0.1 --- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d73e8522f..c45a179dc 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.1 - 2022-03-04 +- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678] + +[#2678]: https://github.com/actix/actix-web/issues/2678 + ## 3.0.0 - 2022-02-25 ### Dependencies - Updated `actix-*` to Tokio v1-based versions. [#1813] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 751b820e8..3f223d80d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0" +version = "3.0.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 8d414a0fc..aaff7b6f1 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0)](https://docs.rs/actix-http/3.0.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.1)](https://docs.rs/actix-http/3.0.1) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0/status.svg)](https://deps.rs/crate/actix-http/3.0.0) +[![dependency status](https://deps.rs/crate/actix-http/3.0.1/status.svg)](https://deps.rs/crate/actix-http/3.0.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 0fa4d999d92a04cd2a9ddef10486fb8bb92700bc Mon Sep 17 00:00:00 2001 From: Santiago Date: Sat, 5 Mar 2022 23:24:21 +0100 Subject: [PATCH 411/861] fix(actix-http): encode correctly camel case header with n+2 hyphens (#2683) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 8 ++++++-- actix-http/src/h1/encoder.rs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c45a179dc..dc5ff4a85 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Fixed +- Encode correctly camel case header with n+2 hyphens [#2683] + +[#2683]: https://github.com/actix/actix-web/issues/2683 ## 3.0.1 - 2022-03-04 @@ -750,10 +754,10 @@ - Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` due to deprecate of resolver actor. [#1813] - Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. - due to the removal of this type from `tokio-openssl` crate. openssl handshake + due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] - Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. - Due to this change `actix_threadpool::BlockingError` type is moved into + Due to this change `actix_threadpool::BlockingError` type is moved into `actix_http::error` module. [#1878] [#1813]: https://github.com/actix/actix-web/pull/1813 diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index ba98f4641..21cfd75c4 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -517,6 +517,7 @@ unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) { if let Some(c @ b'a'..=b'z') = iter.next() { buffer[index] = c & 0b1101_1111; } + index += 1; } index += 1; @@ -528,7 +529,7 @@ mod tests { use std::rc::Rc; use bytes::Bytes; - use http::header::AUTHORIZATION; + use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS}; use super::*; use crate::{ @@ -559,6 +560,9 @@ mod tests { head.headers .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); + head.headers + .insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1")); + let mut head = RequestHeadType::Owned(head); let _ = head.encode_headers( @@ -574,6 +578,7 @@ mod tests { assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Date: date\r\n")); + assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n")); let _ = head.encode_headers( &mut bytes, From 62fbd225bc5c36fd682389910ff5e13bd44e8c58 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 22:26:19 +0000 Subject: [PATCH 412/861] prepare actix-http release 3.0.2 --- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index dc5ff4a85..7be5dccff 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.2 - 2022-03-05 ### Fixed -- Encode correctly camel case header with n+2 hyphens [#2683] +- Fix encoding camel-case header names with more than one hyphen. [#2683] [#2683]: https://github.com/actix/actix-web/issues/2683 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3f223d80d..b365ff182 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.1" +version = "3.0.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index aaff7b6f1..3a2483191 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.1)](https://docs.rs/actix-http/3.0.1) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.2)](https://docs.rs/actix-http/3.0.2) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.1/status.svg)](https://deps.rs/crate/actix-http/3.0.1) +[![dependency status](https://deps.rs/crate/actix-http/3.0.2/status.svg)](https://deps.rs/crate/actix-http/3.0.2) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8c2fad31647abea48dbbc790983f6cebde4eb2f9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 23:15:33 +0000 Subject: [PATCH 413/861] align hello-world examples --- actix-web/README.md | 17 +++++++++-------- actix-web/src/lib.rs | 19 ++++++++++--------- awc/Cargo.toml | 2 +- awc/src/lib.rs | 2 +- awc/src/responses/response.rs | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/actix-web/README.md b/actix-web/README.md index d0abb3aae..957fb47b8 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -56,18 +56,19 @@ Code: ```rust use actix_web::{get, web, App, HttpServer, Responder}; -#[get("/{id}/{name}/index.html")] -async fn index(params: web::Path<(u32, String)>) -> impl Responder { - let (id, name) = params.into_inner(); - format!("Hello {}! id:{}", name, id) +#[get("/hello/{name}")] +async fn greet(name: web::Path) -> impl Responder { + format!("Hello {name}!") } #[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(index)) - .bind(("127.0.0.1", 8080))? - .run() - .await + HttpServer::new(|| { + App::new().service(greet) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await } ``` diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 34bee7529..4eab24cec 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -4,18 +4,19 @@ //! ```no_run //! use actix_web::{get, web, App, HttpServer, Responder}; //! -//! #[get("/{id}/{name}/index.html")] -//! async fn index(path: web::Path<(u32, String)>) -> impl Responder { -//! let (id, name) = path.into_inner(); -//! format!("Hello {}! id:{}", name, id) +//! #[get("/hello/{name}")] +//! async fn greet(name: web::Path) -> impl Responder { +//! format!("Hello {}!", name) //! } //! -//! #[actix_web::main] +//! #[actix_web::main] // or #[tokio::main] //! async fn main() -> std::io::Result<()> { -//! HttpServer::new(|| App::new().service(index)) -//! .bind("127.0.0.1:8080")? -//! .run() -//! .await +//! HttpServer::new(|| { +//! App::new().service(greet) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await //! } //! ``` //! diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 40d9d34b6..f86aa5543 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -5,7 +5,7 @@ authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", ] -description = "Async HTTP and WebSocket client library built on the Actix ecosystem" +description = "Async HTTP and WebSocket client library" keywords = ["actix", "http", "framework", "async", "web"] categories = [ "network-programming", diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 970ca2d92..3f5e25330 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,4 +1,4 @@ -//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. +//! `awc` is an asynchronous HTTP and WebSocket client library. //! //! # Making a GET request //! ```no_run diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs index 4e6a05f0f..c7c0a6362 100644 --- a/awc/src/responses/response.rs +++ b/awc/src/responses/response.rs @@ -160,7 +160,7 @@ where /// /// # Errors /// `Future` implementation returns error if: - /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB) + /// - content length is greater than [limit](ResponseBody::limit) (default: 2 MiB) /// /// # Examples /// ```no_run From 03456b8a33a4550b94785a7612e16d715755cd00 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 23:43:31 +0000 Subject: [PATCH 414/861] update actix-web-in-http example --- actix-http/examples/actix-web.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/examples/actix-web.rs b/actix-http/examples/actix-web.rs index f8226507f..449e5899b 100644 --- a/actix-http/examples/actix-web.rs +++ b/actix-http/examples/actix-web.rs @@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> { HttpService::build() // pass the app to service builder // map_config is used to map App's configuration to ServiceBuilder - .finish(map_config(app, |_| AppConfig::default())) + // h1 will configure server to only use HTTP/1.1 + .h1(map_config(app, |_| AppConfig::default())) .tcp() })? .run() From 87f627cd5d33fe71833c24803174dcec5806fea2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 7 Mar 2022 16:48:04 +0000 Subject: [PATCH 415/861] improve servicerequest docs --- actix-http/src/responses/builder.rs | 4 +-- actix-web/src/service.rs | 50 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/actix-http/src/responses/builder.rs b/actix-http/src/responses/builder.rs index 4a67423b1..063af92da 100644 --- a/actix-http/src/responses/builder.rs +++ b/actix-http/src/responses/builder.rs @@ -144,7 +144,7 @@ impl ResponseBuilder { self } - /// Set connection type to Upgrade + /// Set connection type to `Upgrade`. #[inline] pub fn upgrade(&mut self, value: V) -> &mut Self where @@ -161,7 +161,7 @@ impl ResponseBuilder { self } - /// Force close connection, even if it is marked as keep-alive + /// Force-close connection, even if it is marked as keep-alive. #[inline] pub fn force_close(&mut self) -> &mut Self { if let Some(parts) = self.inner() { diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 3843abcf8..426e9d62b 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -78,18 +78,18 @@ pub struct ServiceRequest { } impl ServiceRequest { - /// Construct service request + /// Construct `ServiceRequest` from parts. pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self { Self { req, payload } } - /// Deconstruct request into parts + /// Deconstruct `ServiceRequest` into inner parts. #[inline] pub fn into_parts(self) -> (HttpRequest, Payload) { (self.req, self.payload) } - /// Get mutable access to inner `HttpRequest` and `Payload` + /// Returns mutable accessors to inner parts. #[inline] pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) { (&mut self.req, &mut self.payload) @@ -105,9 +105,7 @@ impl ServiceRequest { Self { req, payload } } - /// Construct request from request. - /// - /// The returned `ServiceRequest` would have no payload. + /// Construct `ServiceRequest` with no payload from given `HttpRequest`. #[inline] pub fn from_request(req: HttpRequest) -> Self { ServiceRequest { @@ -116,63 +114,63 @@ impl ServiceRequest { } } - /// Create service response + /// Create `ServiceResponse` from this request and given response. #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { let res = HttpResponse::from(res.into()); ServiceResponse::new(self.req, res) } - /// Create service response for error + /// Create `ServiceResponse` from this request and given error. #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.req, res) } - /// This method returns reference to the request head + /// Returns a reference to the request head. #[inline] pub fn head(&self) -> &RequestHead { self.req.head() } - /// This method returns reference to the request head + /// Returns a mutable reference to the request head. #[inline] pub fn head_mut(&mut self) -> &mut RequestHead { self.req.head_mut() } - /// Request's uri. + /// Returns the request URI. #[inline] pub fn uri(&self) -> &Uri { &self.head().uri } - /// Read the Request method. + /// Returns the request method. #[inline] pub fn method(&self) -> &Method { &self.head().method } - /// Read the Request Version. + /// Returns the request version. #[inline] pub fn version(&self) -> Version { self.head().version } + /// Returns a reference to request headers. #[inline] - /// Returns request's headers. pub fn headers(&self) -> &HeaderMap { &self.head().headers } + /// Returns a mutable reference to request headers. #[inline] - /// Returns mutable request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } - /// The target path of this Request. + /// Returns request path. #[inline] pub fn path(&self) -> &str { self.head().uri.path() @@ -184,7 +182,7 @@ impl ServiceRequest { self.req.query_string() } - /// Peer socket address. + /// Returns peer's socket address. /// /// Peer address is the directly connected peer's socket address. If a proxy is used in front of /// the Actix Web server, then it would be address of this proxy. @@ -197,24 +195,23 @@ impl ServiceRequest { self.head().peer_addr } - /// Get *ConnectionInfo* for the current request. + /// Returns a reference to connection info. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { self.req.connection_info() } - /// Returns a reference to the Path parameters. + /// Returns reference to the Path parameters. /// - /// Params is a container for URL parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. + /// Params is a container for URL parameters. A variable segment is specified in the form + /// `{identifier}`, where the identifier can be used later in a request handler to access the + /// matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { self.req.match_info() } - /// Returns a mutable reference to the Path parameters. + /// Returns a mutable reference to the path match information. #[inline] pub fn match_info_mut(&mut self) -> &mut Path { self.req.match_info_mut() @@ -232,13 +229,13 @@ impl ServiceRequest { self.req.match_pattern() } - /// Get a reference to a `ResourceMap` of current application. + /// Returns a reference to the application's resource map. #[inline] pub fn resource_map(&self) -> &ResourceMap { self.req.resource_map() } - /// Service configuration + /// Returns a reference to the application's configuration. #[inline] pub fn app_config(&self) -> &AppConfig { self.req.app_config() @@ -262,6 +259,7 @@ impl ServiceRequest { self.req.conn_data() } + /// Return request cookies. #[cfg(feature = "cookies")] #[inline] pub fn cookies(&self) -> Result>>, CookieParseError> { From 8ddb24b49b0148f12524ec9cb3ff9ff67bfce743 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Mar 2022 16:51:40 +0000 Subject: [PATCH 416/861] prepare awc release 3.0.0 (#2684) --- actix-http-test/Cargo.toml | 10 +-- actix-http/CHANGES.md | 7 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 +- actix-http/src/h1/decoder.rs | 22 +++++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 10 +-- actix-web-actors/Cargo.toml | 2 +- actix-web/CHANGES.md | 2 +- actix-web/Cargo.toml | 10 +-- awc/CHANGES.md | 98 +++++++++++++++++++++++++++++ awc/Cargo.toml | 14 ++--- awc/README.md | 4 +- awc/src/client/connector.rs | 7 ++- awc/src/client/h1proto.rs | 10 ++- awc/src/connect.rs | 29 +++++++++ awc/src/lib.rs | 55 +++++++++-------- awc/src/middleware/redirect.rs | 109 +++++++++++++++++++++++++-------- awc/src/request.rs | 2 +- 19 files changed, 306 insertions(+), 93 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e2a2bcc3d..6f7563ffa 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,13 +29,13 @@ default = [] openssl = ["tls-openssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0" +actix-service = "2" actix-codec = "0.5" actix-tls = "3" -actix-utils = "3.0.0" +actix-utils = "3" actix-rt = "2.2" actix-server = "2" -awc = { version = "3.0.0-beta.21", default-features = false } +awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } -actix-http = "3.0.0" +actix-web = { version = "4", default-features = false, features = ["cookies"] } +actix-http = "3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7be5dccff..ab7f1e332 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,13 @@ ## Unreleased - 2021-xx-xx +## 3.0.3 - 2022-03-08 +### Fixed +- Allow spaces between header name and colon when parsing responses. [#2684] + +[#2684]: https://github.com/actix/actix-web/issues/2684 + + ## 3.0.2 - 2022-03-05 ### Fixed - Fix encoding camel-case header names with more than one hyphen. [#2683] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b365ff182..7006d92d7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.2" +version = "3.0.3" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 3a2483191..afe445ebd 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.2)](https://docs.rs/actix-http/3.0.2) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.3)](https://docs.rs/actix-http/3.0.3) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.2/status.svg)](https://deps.rs/crate/actix-http/3.0.2) +[![dependency status](https://deps.rs/crate/actix-http/3.0.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 17b9b695c..0e444756e 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -293,22 +293,35 @@ impl MessageType for ResponseHead { let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY; let (len, ver, status, h_len) = { - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY; + // SAFETY: + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the + // type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which + // do not require initialization. + let mut parsed = unsafe { + MaybeUninit::<[MaybeUninit>; MAX_HEADERS]>::uninit() + .assume_init() + }; - let mut res = httparse::Response::new(&mut parsed); - match res.parse(src)? { + let mut res = httparse::Response::new(&mut []); + + let mut config = httparse::ParserConfig::default(); + config.allow_spaces_after_header_name_in_responses(true); + + match config.parse_response_with_uninit_headers(&mut res, src, &mut parsed)? { httparse::Status::Complete(len) => { let version = if res.version.unwrap() == 1 { Version::HTTP_11 } else { Version::HTTP_10 }; + let status = StatusCode::from_u16(res.code.unwrap()) .map_err(|_| ParseError::Status)?; HeaderIndex::record(src, res.headers, &mut headers); (len, version, status, res.headers.len()) } + httparse::Status::Partial => { return if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); @@ -360,9 +373,6 @@ pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex { pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS]; -pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] = - [httparse::EMPTY_HEADER; MAX_HEADERS]; - impl HeaderIndex { pub(crate) fn record( bytes: &[u8], diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 450a57fa9..e93e22941 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -14,7 +14,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-utils = "3.0.0" +actix-utils = "3" actix-web = { version = "4.0.0", default-features = false } bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index af4aff56a..9938be67d 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" -actix-http = "3.0.0" +actix-http = "3" actix-http-test = "3.0.0-beta.13" actix-rt = "2.1" -actix-service = "2.0.0" -actix-utils = "3.0.0" -actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] } +actix-service = "2" +actix-utils = "3" +actix-web = { version = "4", default-features = false, features = ["cookies"] } +awc = { version = "3", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 225326565..c939f6ab5 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.13" -awc = { version = "3.0.0-beta.21", default-features = false } +awc = { version = "3", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index bf5caee86..2461cb3a1 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -15,7 +15,7 @@ - Updated `cookie` to `0.16`. [#2555] - Updated `language-tags` to `0.3`. - Updated `rand` to `0.8`. -- Updated `rustls` to `0.20.0`. [#2414] +- Updated `rustls` to `0.20`. [#2414] - Updated `tokio` to `1`. ### Added diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 6e453026a..7bbeec64d 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,9 +71,9 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0", features = ["http2", "ws"] } -actix-router = "0.5.0" -actix-web-codegen = { version = "4.0.0", optional = true } +actix-http = { version = "3", features = ["http2", "ws"] } +actix-router = "0.5" +actix-web-codegen = { version = "4", optional = true } ahash = "0.7" bytes = "1" @@ -100,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0" +actix-files = "0.6" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.21", features = ["openssl"] } +awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3fd59512a..ebc0dbe61 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,103 @@ ## Unreleased - 2021-xx-xx +## 3.0.0 - 2022-03-07 +### Dependencies +- Updated `actix-*` to Tokio v1-based versions. [#1813] +- Updated `bytes` to `1.0`. [#1813] +- Updated `cookie` to `0.16`. [#2555] +- Updated `rand` to `0.8`. +- Updated `rustls` to `0.20`. [#2414] +- Updated `tokio` to `1`. + +### Added +- `trust-dns` crate feature to enable `trust-dns-resolver` as client DNS resolver; disabled by default. [#1969] +- `cookies` crate feature; enabled by default. [#2619] +- `compress-brotli` crate feature; enabled by default. [#2250] +- `compress-gzip` crate feature; enabled by default. [#2250] +- `compress-zstd` crate feature; enabled by default. [#2250] +- `client::Connector::handshake_timeout()` for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] +- `Client::headers()` to get default mut reference of `HeaderMap` of client object. [#2114] +- `ClientResponse::timeout()` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address()` for binding to a local IP address for this client. [#2024] +- `ClientRequest::insert_header()` method which allows using typed and untyped headers. [#1869] +- `ClientRequest::append_header()` method which allows using typed and untyped headers. [#1869] +- `ClientBuilder::add_default_header()` (and deprecate `ClientBuilder::header()`). [#2510] + +### Changed +- `client::Connector` type now only has one generic type for `actix_service::Service`. [#2063] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::{header => append_header, set => insert_header}`. These methods now take a `TryIntoHeaderPair`. [#2094] +- `ClientBuilder::connector()` method now takes `Connector` type. [#2008] +- Basic auth now accepts blank passwords as an empty string instead of an `Option`. [#2050] +- Relax default timeout for `Connector` to 5 seconds (up from 1 second). [#1905] +- `*::send_json()` and `*::send_form()` methods now receive `impl Serialize`. [#2553] +- `FrozenClientRequest::extra_header()` now uses receives an `impl TryIntoHeaderPair`. [#2553] +- Rename `Connector::{ssl => openssl}()`. [#2503] +- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546] +- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546] +- Minimum supported Rust version (MSRV) is now 1.54. + +### Fixed +- Send headers along with redirected requests. [#2310] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553] +- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546] + +### Removed +- `compress` crate feature. [#2250] +- `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] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- `ClientBuilder::default` function [#2008] + +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + +[#1813]: https://github.com/actix/actix-web/pull/1813 +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1931]: https://github.com/actix/actix-web/pull/1931 +[#1969]: https://github.com/actix/actix-web/pull/1969 +[#1969]: https://github.com/actix/actix-web/pull/1969 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#2008]: https://github.com/actix/actix-web/pull/2008 +[#2024]: https://github.com/actix/actix-web/pull/2024 +[#2050]: https://github.com/actix/actix-web/pull/2050 +[#2063]: https://github.com/actix/actix-web/pull/2063 +[#2081]: https://github.com/actix/actix-web/pull/2081 +[#2081]: https://github.com/actix/actix-web/pull/2081 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2114]: https://github.com/actix/actix-web/pull/2114 +[#2116]: https://github.com/actix/actix-web/pull/2116 +[#2148]: https://github.com/actix/actix-web/pull/2148 +[#2250]: https://github.com/actix/actix-web/pull/2250 +[#2310]: https://github.com/actix/actix-web/pull/2310 +[#2414]: https://github.com/actix/actix-web/pull/2414 +[#2425]: https://github.com/actix/actix-web/pull/2425 +[#2474]: https://github.com/actix/actix-web/pull/2474 +[#2503]: https://github.com/actix/actix-web/pull/2503 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2546]: https://github.com/actix/actix-web/pull/2546 +[#2553]: https://github.com/actix/actix-web/pull/2553 +[#2555]: https://github.com/actix/actix-web/pull/2555 + + +

+3.0.0 Pre-Releases + ## 3.0.0-beta.21 - 2022-02-16 - No significant changes since `3.0.0-beta.20`. @@ -170,6 +267,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 +
## 2.0.3 - 2020-11-29 ### Fixed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f86aa5543..9dd29e4b7 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.21" +version = "3.0.0" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -59,11 +59,11 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" -actix-service = "2.0.0" -actix-http = { version = "3.0.0", features = ["http2", "ws"] } +actix-service = "2" +actix-http = { version = "3", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } -actix-utils = "3.0.0" +actix-utils = "3" ahash = "0.7" base64 = "0.13" @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0", features = ["openssl"] } +actix-http = { version = "3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } -actix-utils = "3.0.0" -actix-web = { version = "4.0.0", features = ["openssl"] } +actix-utils = "3" +actix-web = { version = "4", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/README.md b/awc/README.md index 417647e62..db70f7332 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0)](https://docs.rs/awc/3.0.0) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0/status.svg)](https://deps.rs/crate/awc/3.0.0) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 26c62b924..51d6e180b 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -246,7 +246,12 @@ where /// /// The default limit size is 100. pub fn limit(mut self, limit: usize) -> Self { - self.config.limit = limit; + if limit == 0 { + self.config.limit = u32::MAX as usize; + } else { + self.config.limit = limit; + } + self } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 4f6a87ac5..8738c2f7f 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -83,12 +83,12 @@ where false }; - framed.send((head, body.size()).into()).await?; - let mut pin_framed = Pin::new(&mut framed); // special handle for EXPECT request. let (do_send, mut res_head) = if is_expect { + pin_framed.send((head, body.size()).into()).await?; + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) .await .ok_or(ConnectError::Disconnected)??; @@ -97,13 +97,17 @@ where // and current head would be used as final response head. (head.status == StatusCode::CONTINUE, Some(head)) } else { + pin_framed.feed((head, body.size()).into()).await?; + (true, None) }; if do_send { // send request body match body.size() { - BodySize::None | BodySize::Sized(0) => {} + BodySize::None | BodySize::Sized(0) => { + poll_fn(|cx| pin_framed.as_mut().flush(cx)).await?; + } _ => send_body(body, pin_framed.as_mut()).await?, }; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index f93014a67..be1ea0fee 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc< pub type BoxedSocket = Box; +/// Combined HTTP and WebSocket request type received by connection service. pub enum ConnectRequest { + /// Standard HTTP request. + /// + /// Contains the request head, body type, and optional pre-resolved socket address. Client(RequestHeadType, AnyBody, Option), + + /// Tunnel used by WebSocket connection requests. + /// + /// Contains the request head and optional pre-resolved socket address. Tunnel(RequestHead, Option), } +/// Combined HTTP response & WebSocket tunnel type returned from connection service. pub enum ConnectResponse { + /// Standard HTTP response. Client(ClientResponse), + + /// Tunnel used for WebSocket communication. + /// + /// Contains response head and framed HTTP/1.1 codec. Tunnel(ResponseHead, Framed), } impl ConnectResponse { + /// Unwraps type into HTTP response. + /// + /// # Panics + /// Panics if enum variant is not `Client`. pub fn into_client_response(self) -> ClientResponse { match self { ConnectResponse::Client(res) => res, @@ -50,6 +68,10 @@ impl ConnectResponse { } } + /// Unwraps type into WebSocket tunnel response. + /// + /// # Panics + /// Panics if enum variant is not `Tunnel`. pub fn into_tunnel_response(self) -> (ResponseHead, Framed) { match self { ConnectResponse::Tunnel(head, framed) => (head, framed), @@ -136,30 +158,37 @@ where ConnectRequestProj::Connection { fut, req } => { let connection = ready!(fut.poll(cx))?; let req = req.take().unwrap(); + match req { ConnectRequest::Client(head, body, ..) => { // send request let fut = ConnectRequestFuture::Client { fut: connection.send_request(head, body), }; + self.set(fut); } + ConnectRequest::Tunnel(head, ..) => { // send request let fut = ConnectRequestFuture::Tunnel { fut: connection.open_tunnel(RequestHeadType::from(head)), }; + self.set(fut); } } + self.poll(cx) } + ConnectRequestProj::Client { fut } => { let (head, payload) = ready!(fut.as_mut().poll(cx))?; Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new( head, payload, )))) } + ConnectRequestProj::Tunnel { fut } => { let (head, framed) = ready!(fut.as_mut().poll(cx))?; let framed = framed.into_map_io(|io| Box::new(io) as _); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 3f5e25330..8d6ea759a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,22 +1,25 @@ //! `awc` is an asynchronous HTTP and WebSocket client library. //! -//! # Making a GET request +//! # `GET` Requests //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! // create client //! let mut client = awc::Client::default(); -//! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .insert_header(("User-Agent", "Actix-web")) -//! .send() // <- Send http request -//! .await?; //! -//! println!("Response: {:?}", response); +//! // construct request +//! let req = client.get("http://www.rust-lang.org") +//! .insert_header(("User-Agent", "awc/3.0")); +//! +//! // send request and await response +//! let res = req.send().await?; +//! println!("Response: {:?}", res); //! # Ok(()) //! # } //! ``` //! -//! # Making POST requests -//! ## Raw body contents +//! # `POST` Requests +//! ## Raw Body //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -28,20 +31,6 @@ //! # } //! ``` //! -//! ## Forms -//! ```no_run -//! # #[actix_rt::main] -//! # async fn main() -> Result<(), awc::error::SendRequestError> { -//! let params = [("foo", "bar"), ("baz", "quux")]; -//! -//! let mut client = awc::Client::default(); -//! let response = client.post("http://httpbin.org/post") -//! .send_form(¶ms) -//! .await?; -//! # Ok(()) -//! # } -//! ``` -//! //! ## JSON //! ```no_run //! # #[actix_rt::main] @@ -59,6 +48,20 @@ //! # } //! ``` //! +//! ## URL Encoded Form +//! ```no_run +//! # #[actix_rt::main] +//! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! let params = [("foo", "bar"), ("baz", "quux")]; +//! +//! let mut client = awc::Client::default(); +//! let response = client.post("http://httpbin.org/post") +//! .send_form(¶ms) +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! //! # Response Compression //! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! @@ -76,11 +79,12 @@ //! //! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding //! -//! # WebSocket support +//! # WebSockets //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { -//! use futures_util::{sink::SinkExt, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") //! .connect() @@ -89,8 +93,9 @@ //! connection //! .send(awc::ws::Message::Text("Echo".into())) //! .await?; +//! //! let response = connection.next().await.unwrap()?; -//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); +//! assert_eq!(response, awc::ws::Frame::Text("Echo".into())); //! # Ok(()) //! # } //! ``` diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index ac6690471..d48822168 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -161,7 +161,8 @@ where | StatusCode::SEE_OTHER | StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT - if *max_redirect_times > 0 => + if *max_redirect_times > 0 + && res.headers().contains_key(header::LOCATION) => { let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT || res.head().status == StatusCode::PERMANENT_REDIRECT; @@ -245,26 +246,32 @@ where } fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result { - let uri = res - .headers() - .get(header::LOCATION) - .map(|value| { - // try to parse the location to a full uri - let uri = Uri::try_from(value.as_bytes()) - .map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?; - if uri.scheme().is_none() || uri.authority().is_none() { - let uri = Uri::builder() - .scheme(prev_uri.scheme().cloned().unwrap()) - .authority(prev_uri.authority().cloned().unwrap()) - .path_and_query(value.as_bytes()) - .build()?; - Ok::<_, SendRequestError>(uri) - } else { - Ok(uri) - } - }) - // TODO: this error type is wrong. - .ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??; + // responses without this header are not processed + let location = res.headers().get(header::LOCATION).unwrap(); + + // try to parse the location and resolve to a full URI but fall back to default if it fails + let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default()); + + let uri = if uri.scheme().is_none() || uri.authority().is_none() { + let builder = Uri::builder() + .scheme(prev_uri.scheme().cloned().unwrap()) + .authority(prev_uri.authority().cloned().unwrap()); + + // when scheme or authority is missing treat the location value as path and query + // recover error where location does not have leading slash + let path = if location.as_bytes().starts_with(b"/") { + location.as_bytes().to_owned() + } else { + [b"/", location.as_bytes()].concat() + }; + + builder + .path_and_query(path) + .build() + .map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))? + } else { + uri + }; Ok(uri) } @@ -287,10 +294,13 @@ mod tests { use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use super::*; - use crate::{http::header::HeaderValue, ClientBuilder}; + use crate::{ + http::{header::HeaderValue, StatusCode}, + ClientBuilder, + }; #[actix_rt::test] - async fn test_basic_redirect() { + async fn basic_redirect() { let client = ClientBuilder::new() .disable_redirects() .wrap(Redirect::new().max_redirect_times(10)) @@ -315,6 +325,44 @@ mod tests { assert_eq!(res.status().as_u16(), 400); } + #[actix_rt::test] + async fn redirect_relative_without_leading_slash() { + let client = ClientBuilder::new().finish(); + + let srv = actix_test::start(|| { + App::new() + .service(web::resource("/").route(web::to(|| async { + HttpResponse::Found() + .insert_header(("location", "abc/")) + .finish() + }))) + .service( + web::resource("/abc/") + .route(web::to(|| async { HttpResponse::Accepted().finish() })), + ) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::ACCEPTED); + } + + #[actix_rt::test] + async fn redirect_without_location() { + let client = ClientBuilder::new() + .disable_redirects() + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = actix_test::start(|| { + App::new().service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::Found().finish()) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::FOUND); + } + #[actix_rt::test] async fn test_redirect_limit() { let client = ClientBuilder::new() @@ -328,14 +376,14 @@ mod tests { .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test")) + .insert_header(("location", "/test")) .finish(), ) }))) .service(web::resource("/test").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test2")) + .insert_header(("location", "/test2")) .finish(), ) }))) @@ -345,8 +393,15 @@ mod tests { }); let res = client.get(srv.url("/")).send().await.unwrap(); - - assert_eq!(res.status().as_u16(), 302); + assert_eq!(res.status(), StatusCode::FOUND); + assert_eq!( + res.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(), + "/test2" + ); } #[actix_rt::test] diff --git a/awc/src/request.rs b/awc/src/request.rs index 8bcf1ee01..102db3c16 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!( f, - "\nClientRequest {:?} {}:{}", + "\nClientRequest {:?} {} {}", self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; From be986d96b387f9a040904a6385e9500a4eb5bb8f Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Tue, 8 Mar 2022 18:42:42 +0100 Subject: [PATCH 417/861] bump `regex` requirement to `1.5.5` due to security advisory (#2687) --- actix-web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7bbeec64d..093c000b4 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -90,7 +90,7 @@ once_cell = "1.5" log = "0.4" mime = "0.3" pin-project-lite = "0.2.7" -regex = "1.4" +regex = "1.5.5" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" From dce943851802ea792a3fd233110f011b7b7a1d6a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:11:06 +0000 Subject: [PATCH 418/861] document with ws feature --- actix-http/Cargo.toml | 2 +- actix-http/src/payload.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7006d92d7..8ac3465a2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -20,7 +20,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] +features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] [lib] name = "actix_http" diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 33d9ec6f5..ee0128af4 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -13,7 +13,8 @@ use crate::error::PayloadError; /// A boxed payload stream. pub type BoxedPayloadStream = Pin>>>; -#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] +#[doc(hidden)] +#[deprecated(since = "3.0.0", note = "Renamed to `BoxedPayloadStream`.")] pub type PayloadStream = BoxedPayloadStream; #[cfg(not(feature = "http2"))] From 5611b98c0d46537bcb330774c74f814cde6bad31 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:13:39 +0000 Subject: [PATCH 419/861] prepare actix-http release 3.0.4 --- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ab7f1e332..71132c6b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.4 - 2022-03-09 +### Fixed +- Document on docs.rs with `ws` feature enabled. + + ## 3.0.3 - 2022-03-08 ### Fixed - Allow spaces between header name and colon when parsing responses. [#2684] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8ac3465a2..7c9284836 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.3" +version = "3.0.4" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index afe445ebd..bf0b7c824 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.3)](https://docs.rs/actix-http/3.0.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From a35804b89f5b08b11304d4fa3e4ca37c9a4f6627 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:05:03 +0000 Subject: [PATCH 420/861] update files tokio-uring to 0.3 --- actix-files/CHANGES.md | 3 ++- actix-files/Cargo.toml | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 4d4c790e8..2fdc7ba34 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx -- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645] +- Update `tokio-uring` dependency to `0.3`. +- Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index e7e6aea23..8f856c109 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -39,10 +39,12 @@ mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" +# experimental-io-uring tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } +actix-server = "2.1" # ensure matching tokio-uring versions [dev-dependencies] -actix-rt = "2.2" +actix-rt = "2.7" actix-test = "0.1.0-beta.13" actix-web = "4.0.0" tempfile = "3.2" From 1fd90f0b1098bf6b6bf7219b74e7b8adb29c851f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:29:26 +0000 Subject: [PATCH 421/861] Implement getters for named file fields (#2689) Co-authored-by: Janis Goldschmidt --- actix-files/CHANGES.md | 2 ++ actix-files/src/named.rs | 55 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2fdc7ba34..7e99c2ae1 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,9 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] +[#2021]: https://github.com/actix/actix-web/pull/2021 [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6f3c6e1c8..7ab29e5c8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -23,6 +23,7 @@ use actix_web::{ use bitflags::bitflags; use derive_more::{Deref, DerefMut}; use futures_core::future::LocalBoxFuture; +use mime::Mime; use mime_guess::from_path; use crate::{encoding::equiv_utf8_text, range::HttpRange}; @@ -76,8 +77,8 @@ pub struct NamedFile { pub(crate) md: Metadata, pub(crate) flags: Flags, pub(crate) status_code: StatusCode, - pub(crate) content_type: mime::Mime, - pub(crate) content_disposition: header::ContentDisposition, + pub(crate) content_type: Mime, + pub(crate) content_disposition: ContentDisposition, pub(crate) encoding: Option, } @@ -238,13 +239,13 @@ impl NamedFile { Self::from_file(file, path) } - /// Returns reference to the underlying `File` object. + /// Returns reference to the underlying file object. #[inline] pub fn file(&self) -> &File { &self.file } - /// Retrieve the path of this file. + /// Returns the filesystem path to this file. /// /// # Examples /// ``` @@ -262,6 +263,48 @@ impl NamedFile { self.path.as_path() } + /// Returns the time the file was last modified. + /// + /// Returns `None` only on unsupported platforms; see [`std::fs::Metadata::modified()`]. + /// Therefore, it is usually safe to unwrap this. + #[inline] + pub fn modified(&self) -> Option { + self.modified + } + + /// Returns the filesystem metadata associated with this file. + #[inline] + pub fn metadata(&self) -> &Metadata { + &self.md + } + + /// Returns the `Content-Type` header that will be used when serving this file. + #[inline] + pub fn content_type(&self) -> &Mime { + &self.content_type + } + + /// Returns the `Content-Disposition` that will be used when serving this file. + #[inline] + pub fn content_disposition(&self) -> &ContentDisposition { + &self.content_disposition + } + + /// Returns the `Content-Encoding` that will be used when serving this file. + /// + /// A return value of `None` indicates that the content is not already using a compressed + /// representation and may be subject to compression downstream. + #[inline] + pub fn content_encoding(&self) -> Option { + self.encoding + } + + /// Returns the status code for serving this file. + #[inline] + pub fn status_code(&self) -> &StatusCode { + &self.status_code + } + /// Set response **Status Code** pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; @@ -271,7 +314,7 @@ impl NamedFile { /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred /// from the filename extension. #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { + pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; self } @@ -284,7 +327,7 @@ impl NamedFile { /// filename is taken from the path provided in the `open` method after converting it to UTF-8 /// (using `to_string_lossy`). #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { + pub fn set_content_disposition(mut self, cd: ContentDisposition) -> Self { self.content_disposition = cd; self.flags.insert(Flags::CONTENT_DISPOSITION); self From 745e738955e7b1572f970fb25dfb4de9bb61b985 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:36:57 +0000 Subject: [PATCH 422/861] fix negative impl assertion on 1.60+ see https://github.com/rust-lang/rust/issues/94791 --- actix-files/src/named.rs | 5 ++--- actix-http/Cargo.toml | 1 + actix-http/src/h1/payload.rs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 7ab29e5c8..459670c3a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -210,11 +210,10 @@ impl NamedFile { Self::from_file(file, path) } - #[allow(rustdoc::broken_intra_doc_links)] /// Attempts to open a file asynchronously in read-only mode. /// - /// When the `experimental-io-uring` crate feature is enabled, this will be async. - /// Otherwise, it will be just like [`open`][Self::open]. + /// When the `experimental-io-uring` crate feature is enabled, this will be async. Otherwise, it + /// will behave just like `open`. /// /// # Examples /// ``` diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7c9284836..a063bd1b9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -110,6 +110,7 @@ memchr = "2.4" once_cell = "1.9" rcgen = "0.8" regex = "1.3" +rustversion = "1" rustls-pemfile = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 4d031c15a..5a93e9051 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,10 @@ mod tests { assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); assert_impl_all!(Inner: Unpin, Send, Sync); + #[rustversion::before(1.60)] assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); + #[rustversion::since(1.60)] + assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { From a03a2a0076d16dd80467b6c91697baabfeb66ddf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:54:06 +0000 Subject: [PATCH 423/861] deprecate `NamedFile::set_status_code` --- actix-files/src/lib.rs | 27 +++++++++++++++++++++++++-- actix-files/src/named.rs | 17 ++++++----------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 41113f2ab..40327e5e8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -364,20 +364,43 @@ mod tests { ); } + #[allow(deprecated)] #[actix_rt::test] - async fn test_named_file_status_code_text() { - let mut file = NamedFile::open_async("Cargo.toml") + async fn status_code_customize_same_output() { + let file1 = NamedFile::open_async("Cargo.toml") .await .unwrap() .set_status_code(StatusCode::NOT_FOUND); + + let file2 = NamedFile::open_async("Cargo.toml") + .await + .unwrap() + .customize() + .with_status(StatusCode::NOT_FOUND); + + let req = TestRequest::default().to_http_request(); + let res1 = file1.respond_to(&req); + let res2 = file2.respond_to(&req); + + assert_eq!(res1.status(), StatusCode::NOT_FOUND); + assert_eq!(res2.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_named_file_status_code_text() { + let mut file = NamedFile::open_async("Cargo.toml").await.unwrap(); + { file.file(); let _f: &File = &file; } + { let _f: &mut File = &mut file; } + let file = file.customize().with_status(StatusCode::NOT_FOUND); + let req = TestRequest::default().to_http_request(); let resp = file.respond_to(&req); assert_eq!( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 459670c3a..5580e6f7e 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -298,20 +298,15 @@ impl NamedFile { self.encoding } - /// Returns the status code for serving this file. - #[inline] - pub fn status_code(&self) -> &StatusCode { - &self.status_code - } - - /// Set response **Status Code** + /// Set response status code. + #[deprecated(since = "0.7.0", note = "Prefer `Responder::customize()`.")] pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; self } - /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred - /// from the filename extension. + /// Sets the `Content-Type` header that will be used when serving this file. By default the + /// `Content-Type` is inferred from the filename extension. #[inline] pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; @@ -332,9 +327,9 @@ impl NamedFile { self } - /// Disable `Content-Disposition` header. + /// Disables `Content-Disposition` header. /// - /// By default Content-Disposition` header is enabled. + /// By default, the `Content-Disposition` header is sent. #[inline] pub fn disable_content_disposition(mut self) -> Self { self.flags.remove(Flags::CONTENT_DISPOSITION); From 80d222aa78717893d98e6f6982b16a7a8d7ee95c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:12:29 +0000 Subject: [PATCH 424/861] use tracing in actix-http --- actix-http/Cargo.toml | 4 +-- actix-http/README.md | 2 +- actix-http/examples/echo.rs | 3 +- actix-http/examples/hello-world.rs | 9 +++-- actix-http/examples/streaming-error.rs | 3 +- actix-http/examples/ws.rs | 9 ++--- actix-http/src/encoding/encoder.rs | 9 ++--- actix-http/src/h1/chunked.rs | 5 +-- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 49 +++++++++++++------------- actix-http/src/h1/service.rs | 9 ++--- actix-http/src/h1/timer.rs | 7 ++-- actix-http/src/h2/dispatcher.rs | 4 +-- actix-http/src/h2/service.rs | 4 +-- actix-http/src/service.rs | 11 +++--- actix-http/src/ws/codec.rs | 3 +- actix-http/src/ws/dispatcher.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 ++- actix-http/tests/test_rustls.rs | 2 ++ actix-web/src/server.rs | 6 ++-- 21 files changed, 80 insertions(+), 69 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a063bd1b9..6d410e46f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,11 +73,11 @@ httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 h2 = { version = "0.3.9", optional = true } @@ -121,7 +121,7 @@ tokio = { version = "1.8.4", features = ["net", "rt", "macros"] } [[example]] name = "ws" -required-features = ["rustls"] +required-features = ["ws", "rustls"] [[bench]] name = "write-camel-case" diff --git a/actix-http/README.md b/actix-http/README.md index bf0b7c824..14a7013db 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -25,7 +25,7 @@ use actix_http::{HttpService, Response}; use actix_server::Server; use futures_util::future; use http::header::HeaderValue; -use log::info; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 58de64530..ae6f00cce 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -5,6 +5,7 @@ use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; use http::header::HeaderValue; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -22,7 +23,7 @@ async fn main() -> io::Result<()> { body.extend_from_slice(&item?); } - log::info!("request body: {:?}", body); + info!("request body: {:?}", body); let res = Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 1a83d4d9c..c749cdd00 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,9 +1,8 @@ use std::{convert::Infallible, io, time::Duration}; -use actix_http::{ - header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, -}; +use actix_http::{header::HeaderValue, HttpService, Request, Response, StatusCode}; use actix_server::Server; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -18,12 +17,12 @@ async fn main() -> io::Result<()> { ext.insert(42u32); }) .finish(|req: Request| async move { - log::info!("{:?}", req); + info!("{:?}", req); let mut res = Response::build(StatusCode::OK); res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); - let forty_two = req.extensions().get::().unwrap().to_string(); + let forty_two = req.conn_data::().unwrap().to_string(); res.insert_header(( "x-forty-two", HeaderValue::from_str(&forty_two).unwrap(), diff --git a/actix-http/examples/streaming-error.rs b/actix-http/examples/streaming-error.rs index 3988cbac2..8c8a249cb 100644 --- a/actix-http/examples/streaming-error.rs +++ b/actix-http/examples/streaming-error.rs @@ -12,6 +12,7 @@ use actix_http::{body::BodyStream, HttpService, Response}; use actix_server::Server; use async_stream::stream; use bytes::Bytes; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -21,7 +22,7 @@ async fn main() -> io::Result<()> { .bind("streaming-error", ("127.0.0.1", 8080), || { HttpService::build() .finish(|req| async move { - log::info!("{:?}", req); + info!("{:?}", req); let res = Response::ok(); Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index d70e43314..c4f0503cd 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -17,6 +17,7 @@ use actix_server::Server; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::{ready, Stream}; +use tracing::{info, trace}; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -34,13 +35,13 @@ async fn main() -> io::Result<()> { } async fn handler(req: Request) -> Result>, Error> { - log::info!("handshaking"); + info!("handshaking"); let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 - log::info!("responding"); - Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?) + info!("responding"); + res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new()))) } struct Heartbeat { @@ -61,7 +62,7 @@ impl Stream for Heartbeat { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - log::trace!("poll"); + trace!("poll"); ready!(self.as_mut().interval.poll_tick(cx)); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0c81ffe1b..0bbb1c106 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -17,6 +17,7 @@ use pin_project_lite::pin_project; #[cfg(feature = "compress-gzip")] use flate2::write::{GzEncoder, ZlibEncoder}; +use tracing::trace; #[cfg(feature = "compress-zstd")] use zstd::stream::write::Encoder as ZstdEncoder; @@ -356,7 +357,7 @@ impl ContentEncoder { ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding br encoding: {}", err); + trace!("Error decoding br encoding: {}", err); Err(err) } }, @@ -365,7 +366,7 @@ impl ContentEncoder { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding gzip encoding: {}", err); + trace!("Error decoding gzip encoding: {}", err); Err(err) } }, @@ -374,7 +375,7 @@ impl ContentEncoder { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding deflate encoding: {}", err); + trace!("Error decoding deflate encoding: {}", err); Err(err) } }, @@ -383,7 +384,7 @@ impl ContentEncoder { ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding ztsd encoding: {}", err); + trace!("Error decoding ztsd encoding: {}", err); Err(err) } }, diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 7d0532fcd..c1dd4b283 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -1,6 +1,7 @@ use std::{io, task::Poll}; use bytes::{Buf as _, Bytes, BytesMut}; +use tracing::{debug, trace}; macro_rules! byte ( ($rdr:ident) => ({ @@ -76,7 +77,7 @@ impl ChunkedState { Poll::Ready(Ok(ChunkedState::Size)) } None => { - log::debug!("chunk size would overflow u64"); + debug!("chunk size would overflow u64"); Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size line: Size is too big", @@ -124,7 +125,7 @@ impl ChunkedState { rem: &mut u64, buf: &mut Option, ) -> Poll> { - log::trace!("Chunked read, remaining={:?}", rem); + trace!("Chunked read, remaining={:?}", rem); let len = rdr.len() as u64; if len == 0 { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 0e444756e..a9443997e 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -6,7 +6,7 @@ use http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Uri, Version, }; -use log::{debug, error, trace}; +use tracing::{debug, error, trace}; use super::chunked::ChunkedState; use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead}; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 648cf14d7..dea8a4beb 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,6 +15,7 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; +use tracing::{debug, error, trace}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -336,7 +337,7 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - log::error!("write zero; closing"); + error!("write zero; closing"); return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))); } @@ -568,7 +569,7 @@ where } StateProj::ExpectCall { fut } => { - log::trace!(" calling expect service"); + trace!(" calling expect service"); match fut.poll(cx) { // expect resolved. write continue to buffer and set InnerDispatcher state @@ -697,12 +698,12 @@ where let mut this = self.as_mut().project(); if can_not_read { - log::debug!("cannot read request payload"); + debug!("cannot read request payload"); if let Some(sender) = &this.payload { // ...maybe handler does not want to read any more payload... if let PayloadStatus::Dropped = sender.need_read(cx) { - log::debug!("handler dropped payload early; attempt to clean connection"); + debug!("handler dropped payload early; attempt to clean connection"); // ...in which case poll request payload a few times loop { match this.codec.decode(this.read_buf)? { @@ -716,7 +717,7 @@ where // connection is in clean state for next request Message::Chunk(None) => { - log::debug!("connection successfully cleaned"); + debug!("connection successfully cleaned"); // reset dispatcher state let _ = this.payload.take(); @@ -737,7 +738,7 @@ where // not enough info to decide if connection is going to be clean or not None => { - log::error!( + error!( "handler did not read whole payload and dispatcher could not \ drain read buf; return 500 and close connection" ); @@ -813,7 +814,7 @@ where if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - log::error!("Internal server error: unexpected payload chunk"); + error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -827,7 +828,7 @@ where if let Some(mut payload) = this.payload.take() { payload.feed_eof(); } else { - log::error!("Internal server error: unexpected eof"); + error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -844,7 +845,7 @@ where Ok(None) => break, Err(ParseError::Io(err)) => { - log::trace!("I/O error: {}", &err); + trace!("I/O error: {}", &err); self.as_mut().client_disconnected(); this = self.as_mut().project(); *this.error = Some(DispatchError::Io(err)); @@ -852,7 +853,7 @@ where } Err(ParseError::TooLarge) => { - log::trace!("request head was too big; returning 431 response"); + trace!("request head was too big; returning 431 response"); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); @@ -872,7 +873,7 @@ where } Err(err) => { - log::trace!("parse error {}", &err); + trace!("parse error {}", &err); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); @@ -903,7 +904,7 @@ where if timer.as_mut().poll(cx).is_ready() { // timeout on first request (slow request) return 408 - log::trace!( + trace!( "timed out on slow request; \ replying with 408 and closing connection" ); @@ -949,7 +950,7 @@ where // keep-alive timer has timed out if timer.as_mut().poll(cx).is_ready() { // no tasks at hand - log::trace!("timer timed out; closing connection"); + trace!("timer timed out; closing connection"); this.flags.insert(Flags::SHUTDOWN); if let Some(deadline) = this.config.client_disconnect_deadline() { @@ -979,7 +980,7 @@ where // timed-out during shutdown; drop connection if timer.as_mut().poll(cx).is_ready() { - log::trace!("timed-out during shutdown"); + trace!("timed-out during shutdown"); return Err(DispatchError::DisconnectTimeout); } } @@ -1138,12 +1139,12 @@ where match this.inner.project() { DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { - log::error!("Upgrade handler error: {}", err); + error!("Upgrade handler error: {}", err); DispatchError::Upgrade }), DispatcherStateProj::Normal { mut inner } => { - log::trace!("start flags: {:?}", &inner.flags); + trace!("start flags: {:?}", &inner.flags); trace_timer_states( "start", @@ -1250,7 +1251,7 @@ where // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { - log::trace!("client is gone; disconnecting"); + trace!("client is gone; disconnecting"); return Poll::Ready(Ok(())); } @@ -1259,14 +1260,14 @@ where // read half is closed; we do not process any responses if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none { - log::trace!("read half closed; start shutdown"); + trace!("read half closed; start shutdown"); inner_p.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors if state_is_none && inner_p.write_buf.is_empty() { if let Some(err) = inner_p.error.take() { - log::error!("stream error: {}", &err); + error!("stream error: {}", &err); return Poll::Ready(Err(err)); } @@ -1295,7 +1296,7 @@ where Poll::Pending }; - log::trace!("end flags: {:?}", &inner.flags); + trace!("end flags: {:?}", &inner.flags); poll } @@ -1310,17 +1311,17 @@ fn trace_timer_states( ka_timer: &TimerState, shutdown_timer: &TimerState, ) { - log::trace!("{} timers:", label); + trace!("{} timers:", label); if head_timer.is_enabled() { - log::trace!(" head {}", &head_timer); + trace!(" head {}", &head_timer); } if ka_timer.is_enabled() { - log::trace!(" keep-alive {}", &ka_timer); + trace!(" keep-alive {}", &ka_timer); } if shutdown_timer.is_enabled() { - log::trace!(" shutdown {}", &shutdown_timer); + trace!(" shutdown {}", &shutdown_timer); } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 43b7919a7..a791ea8c3 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -13,6 +13,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -305,13 +306,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -319,7 +320,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(H1ServiceHandler::new( cfg, @@ -357,7 +358,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP/1 service readiness error: {:?}", err); + error!("HTTP/1 service readiness error: {:?}", err); DispatchError::Service(err) }) } diff --git a/actix-http/src/h1/timer.rs b/actix-http/src/h1/timer.rs index bb69fdb80..0ea70a319 100644 --- a/actix-http/src/h1/timer.rs +++ b/actix-http/src/h1/timer.rs @@ -1,6 +1,7 @@ use std::{fmt, future::Future, pin::Pin, task::Context}; use actix_rt::time::{Instant, Sleep}; +use tracing::trace; #[derive(Debug)] pub(super) enum TimerState { @@ -24,7 +25,7 @@ impl TimerState { pub(super) fn set(&mut self, timer: Sleep, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("setting disabled timer from line {}", line); + trace!("setting disabled timer from line {}", line); } *self = Self::Active { @@ -39,11 +40,11 @@ impl TimerState { pub(super) fn clear(&mut self, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("trying to clear a disabled timer from line {}", line); + trace!("trying to clear a disabled timer from line {}", line); } if matches!(self, Self::Inactive) { - log::trace!("trying to clear an inactive timer from line {}", line); + trace!("trying to clear an inactive timer from line {}", line); } *self = Self::Inactive; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index ce1be537f..85516cccc 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -19,8 +19,8 @@ use h2::{ server::{Connection, SendResponse}, Ping, PingPong, }; -use log::{error, trace}; use pin_project_lite::pin_project; +use tracing::{error, trace, warn}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -143,7 +143,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => log::warn!("{:?}", err), + DispatchError::SendData(err) => warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 653982d37..e526918c7 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -14,7 +14,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::{future::LocalBoxFuture, ready}; -use log::error; +use tracing::{error, trace}; use crate::{ body::{BoxBody, MessageBody}, @@ -355,7 +355,7 @@ where } Err(err) => { - log::trace!("H2 handshake error: {}", err); + trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index b220e55a4..f4fe625a3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -15,6 +15,7 @@ use actix_service::{ }; use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -369,13 +370,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -383,7 +384,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(HttpServiceHandler::new( cfg, @@ -490,7 +491,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP service readiness error: {:?}", err); + error!("HTTP service readiness error: {:?}", err); DispatchError::Service(err) }) } @@ -666,7 +667,7 @@ where self.poll(cx) } Err(err) => { - log::trace!("H2 handshake error: {}", err); + tracing::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 6e7aa7c11..3aa325d6a 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -2,6 +2,7 @@ use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; +use tracing::error; use super::{ frame::Parser, @@ -253,7 +254,7 @@ impl Decoder for Codec { } } _ => { - log::error!("Unfinished fragment {:?}", opcode); + error!("Unfinished fragment {:?}", opcode); Err(ProtocolError::ContinuationFragment(opcode)) } }; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 4c7470d37..2f6b2363b 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -73,8 +73,8 @@ mod inner { use actix_service::{IntoService, Service}; use futures_core::stream::Stream; use local_channel::mpsc; - use log::debug; use pin_project_lite::pin_project; + use tracing::debug; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 78cef1046..17e34e2ba 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; -use log::debug; +use tracing::debug; use super::{ mask::apply_mask, diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 4227f221d..01fe9dd3c 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -3,6 +3,8 @@ use std::{ fmt, }; +use tracing::error; + /// Operation codes defined in [RFC 6455 §11.8]. /// /// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8 @@ -58,7 +60,7 @@ impl From for u8 { Ping => 9, Pong => 10, Bad => { - log::error!("Attempted to convert invalid opcode to u8. This is a bug."); + error!("Attempted to convert invalid opcode to u8. This is a bug."); 8 // if this somehow happens, a close frame will help us tear down quickly } } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 8e59ec65d..550375296 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -212,6 +212,7 @@ async fn h2_content_length() { let value = HeaderValue::from_static("0"); { + #[allow(clippy::single_element_loop)] for &i in &[0] { let req = srv .request(Method::HEAD, srv.surl(&format!("/{}", i))) @@ -226,6 +227,7 @@ async fn h2_content_length() { // assert_eq!(response.headers().get(&header), None); } + #[allow(clippy::single_element_loop)] for &i in &[1] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index bdcfbf48a..99812600c 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -367,9 +367,7 @@ where .local_addr(addr); let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (&*handler)(io as &dyn Any, ext) - }) + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) } else { svc }; @@ -555,7 +553,7 @@ where if let Some(handler) = on_connect_fn.clone() { svc = svc - .on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)); + .on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)); } let fac = factory() From fe5279c77ae8c39792e5865a1478343ebf39b7ec Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:14:14 +0000 Subject: [PATCH 425/861] use tracing in actix-router --- actix-router/Cargo.toml | 2 +- actix-router/src/resource.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 502109114..6fcef125d 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -23,9 +23,9 @@ default = ["http"] bytestring = ">=0.1.5, <2" firestorm = "0.5" http = { version = "0.2.3", optional = true } -log = "0.4" regex = "1.5" serde = "1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index c616b467a..3d121f369 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -7,6 +7,7 @@ use std::{ use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; +use tracing::error; use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath}; @@ -714,10 +715,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -744,7 +742,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!("Dynamic path match but not all segments found: {}", name); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -1038,7 +1036,7 @@ impl ResourceDef { // tail segments in prefixes have no defined semantics #[cfg(not(test))] - log::warn!( + tracing::warn!( "Prefix resources should not have tail segments. \ Use `ResourceDef::new` constructor. \ This may become a panic in the future." @@ -1053,7 +1051,7 @@ impl ResourceDef { // unnamed tail segment #[cfg(not(test))] - log::warn!( + tracing::warn!( "Tail segments must have names. \ Consider `.../{{tail}}*`. \ This may become a panic in the future." From 592b40f914c0fc6fba6c7011edd9111b0e1258dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 15:03:55 +0000 Subject: [PATCH 426/861] move io-uring tests to own job --- .github/workflows/ci.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f41aa972f..7bb911f79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,19 +81,37 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features + - name: Clear the cargo caches + run: | + cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo-cache + + io-uring: + name: io-uring tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.3.0 + - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' timeout-minutes: 60 run: > sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean - cargo-cache + && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" rustdoc: name: doc tests From 478b33b8a30d261a8fffb3fa9e1f5963a0595571 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:00:15 +0000 Subject: [PATCH 427/861] remove nightly io-uring job --- .github/workflows/ci-post-merge.yml | 55 ++++++++++++----------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d37b2c107..2857eb51e 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -78,15 +78,6 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features - - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' - timeout-minutes: 60 - run: > - sudo bash -c "ulimit -Sl 512 - && ulimit -Hl 512 - && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - name: Clear the cargo caches run: | cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean @@ -126,32 +117,32 @@ jobs: with: { command: ci-check-all-feature-powerset-linux } # job currently (1st Feb 2022) segfaults - # coverage: - # name: coverage - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v2 + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - # - name: Install stable - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable-x86_64-unknown-linux-gnu - # profile: minimal - # override: true + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true - # - name: Generate Cargo.lock - # uses: actions-rs/cargo@v1 - # with: { command: generate-lockfile } - # - name: Cache Dependencies - # uses: Swatinem/rust-cache@v1.2.0 + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 - # - name: Generate coverage file - # run: | - # cargo install cargo-tarpaulin --vers "^0.13" - # cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - # - name: Upload to Codecov - # uses: codecov/codecov-action@v1 - # with: { file: cobertura.xml } + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } nextest: name: nextest From 7b27493e4c660f41dcf3cac8a9b0580acf8df4e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:17:49 +0000 Subject: [PATCH 428/861] move coverage to own workflow --- .github/workflows/ci-post-merge.yml | 28 ---------------------- .github/workflows/coverage.yml | 36 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 2857eb51e..9fce98f4c 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -116,34 +116,6 @@ jobs: uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset-linux } - # job currently (1st Feb 2022) segfaults - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - - name: Generate coverage file - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - - name: Upload to Codecov - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } - nextest: name: nextest runs-on: ubuntu-latest diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..137a413d0 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,36 @@ +# disabled because `cargo tarpaulin` currently segfaults + +name: Coverage + +on: + push: + branches: [master] + +jobs: + # job currently (1st Feb 2022) segfaults + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 + + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } From c58f287044c844b55a8767757c4f38d77b489a15 Mon Sep 17 00:00:00 2001 From: nikstur <61635709+nikstur@users.noreply.github.com> Date: Sun, 20 Mar 2022 22:36:19 +0100 Subject: [PATCH 429/861] Removed random superfluous whitespace (#2705) --- actix-web/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index d2df72714..18749d346 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -308,7 +308,7 @@ where /// Registers an app-wide middleware. /// - /// Registers middleware, in the form of a middleware compo nen t (type), that runs during + /// Registers middleware, in the form of a middleware component (type), that runs during /// inbound and/or outbound processing in the request life-cycle (request -> response), /// modifying request/response as necessary, across all requests managed by the `App`. /// From 09cffc093cd755d09a40a14f073f49015aa6e7ad Mon Sep 17 00:00:00 2001 From: mellowagain Date: Tue, 22 Mar 2022 16:30:06 +0100 Subject: [PATCH 430/861] Bump zstd to 0.11 (#2694) Co-authored-by: Rob Ede --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6d410e46f..cd5d3f379 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,7 +94,7 @@ actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.10", optional = true } +zstd = { version = "0.11", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 093c000b4..7793fd8be 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -118,7 +118,7 @@ static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9dd29e4b7..ba0fc14e3 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -110,7 +110,7 @@ static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[example]] name = "client" From e942d3e3b101cde08ccaa30f21020144be5522f8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Mar 2022 13:26:08 +0000 Subject: [PATCH 431/861] update migration guide --- actix-web/MIGRATION-4.0.md | 22 ++++++++++++++++++++++ actix-web/src/data.rs | 7 ++----- actix-web/src/middleware/authors-guide.md | 3 +++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 7192d0bc6..fbeae0680 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -31,6 +31,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) - [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain) - [`web::block`](#webblock) +- ## MSRV @@ -483,3 +484,24 @@ The `web::block` helper has changed return type from roughly `async fn(fn() -> R - let n: u32 = web::block(|| Ok(123)).await?; + let n: u32 = web::block(|| Ok(123)).await??; ``` + +## `HttpResponse` as a `ResponseError` + +The implementation of `ResponseError` for `HttpResponse` has been removed. + +It was common in v3 to use `HttpResponse` as an error type in fallible handlers. The problem is that `HttpResponse` contains no knowledge or reference to the source error. Being able to guarantee that an "error" response actually contains an error reference makes middleware and other parts of Actix Web more effective. + +The error response builders in the `error` module were available in v3 but are now the best method for simple error responses without requiring you to implement the trait on your own custom error types. These builders can receive simple strings and third party errors that can not implement the `ResponseError` trait. + +A few common patterns are affected by this change: + +```diff +- Err(HttpResponse::InternalServerError().finish()) ++ Err(error::ErrorInternalServerError("reason")) + +- Err(HttpResponse::InternalServerError().body(third_party_error.to_string())) ++ Err(error::ErrorInternalServerError(err)) + +- .map_err(|err| HttpResponse::InternalServerError().finish())? ++ .map_err(error::ErrorInternalServerError)? +``` diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs index ce7b1fee6..a689d13e0 100644 --- a/actix-web/src/data.rs +++ b/actix-web/src/data.rs @@ -5,10 +5,7 @@ use actix_utils::future::{err, ok, Ready}; use futures_core::future::LocalBoxFuture; use serde::Serialize; -use crate::{ - dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, - Error, -}; +use crate::{dev::Payload, error, Error, FromRequest, HttpRequest}; /// Data factory. pub(crate) trait DataFactory { @@ -160,7 +157,7 @@ impl FromRequest for Data { req.match_name().unwrap_or_else(|| req.path()) ); - err(ErrorInternalServerError( + err(error::ErrorInternalServerError( "Requested application data is not configured correctly. \ View/enable debug logs for more details.", )) diff --git a/actix-web/src/middleware/authors-guide.md b/actix-web/src/middleware/authors-guide.md index 344523a1a..a8d1edea4 100644 --- a/actix-web/src/middleware/authors-guide.md +++ b/actix-web/src/middleware/authors-guide.md @@ -11,3 +11,6 @@ ## Error Propagation ## When To (Not) Use Middleware + +## Author's References +- `EitherBody` + when is middleware appropriate: https://discord.com/channels/771444961383153695/952016890723729428 From 40048a581158053ee89ced4c7cc21804b828b271 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 28 Mar 2022 23:58:35 +0300 Subject: [PATCH 432/861] rework actix_router::Quoter (#2709) Co-authored-by: Rob Ede --- actix-router/Cargo.toml | 5 + actix-router/benches/quoter.rs | 52 +++++++ actix-router/src/de.rs | 2 +- actix-router/src/quoter.rs | 268 ++++++++++++--------------------- actix-router/src/url.rs | 2 +- 5 files changed, 157 insertions(+), 172 deletions(-) create mode 100644 actix-router/benches/quoter.rs diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 6fcef125d..76f39f631 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -32,7 +32,12 @@ criterion = { version = "0.3", features = ["html_reports"] } firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } +percent-encoding = "2.1" [[bench]] name = "router" harness = false + +[[bench]] +name = "quoter" +harness = false diff --git a/actix-router/benches/quoter.rs b/actix-router/benches/quoter.rs new file mode 100644 index 000000000..c18f1620e --- /dev/null +++ b/actix-router/benches/quoter.rs @@ -0,0 +1,52 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use std::borrow::Cow; + +fn compare_quoters(c: &mut Criterion) { + let mut group = c.benchmark_group("Compare Quoters"); + + let quoter = actix_router::Quoter::new(b"", b""); + let path_quoted = (0..=0x7f) + .map(|c| format!("%{:02X}", c)) + .collect::(); + let path_unquoted = ('\u{00}'..='\u{7f}').collect::(); + + group.bench_function("quoter_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_unquoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_unquoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.bench_function("quoter_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_quoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_quoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.finish(); +} + +criterion_group!(benches, compare_quoters); +criterion_main!(benches); diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index efafd08db..55fcdc912 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -7,7 +7,7 @@ use crate::path::{Path, PathIter}; use crate::{Quoter, ResourcePath}; thread_local! { - static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b""); + static FULL_QUOTER: Quoter = Quoter::new(b"", b""); } macro_rules! unsupported_type { diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 8a1e99e1d..6c929d3ac 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -1,132 +1,89 @@ -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; - -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; - -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; - -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; - -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; - -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; - -const QS: &[u8] = b"+&=;b"; - -/// A quoter +/// Partial percent-decoding. +/// +/// Performs percent-decoding on a slice but can selectively skip decoding certain sequences. +/// +/// # Examples +/// ``` +/// # use actix_router::Quoter; +/// // + is set as a protected character and will not be decoded... +/// let q = Quoter::new(&[], b"+"); +/// +/// // ...but the other encoded characters (like the hyphen below) will. +/// assert_eq!(q.requote(b"/a%2Db%2Bc").unwrap(), b"/a-b%2Bc"); +/// ``` pub struct Quoter { - /// Simple bit-map of safe values in the 0-127 ASCII range. - safe_table: [u8; 16], - /// Simple bit-map of protected values in the 0-127 ASCII range. - protected_table: [u8; 16], + protected_table: AsciiBitmap, } impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut quoter = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for ch in 0..128 { - if ALLOWED.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - - if QS.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - } - - for &ch in safe { - set_bit(&mut quoter.safe_table, ch) - } + /// Constructs a new `Quoter` instance given a set of protected ASCII bytes. + /// + /// The first argument is ignored but is kept for backward compatibility. + /// + /// # Panics + /// Panics if any of the `protected` bytes are not in the 0-127 ASCII range. + pub fn new(_: &[u8], protected: &[u8]) -> Quoter { + let mut protected_table = AsciiBitmap::default(); // prepare protected table for &ch in protected { - set_bit(&mut quoter.safe_table, ch); - set_bit(&mut quoter.protected_table, ch); + protected_table.set_bit(ch); } - quoter + Quoter { protected_table } } - /// Decodes safe percent-encoded sequences from `val`. - /// - /// Returns `None` when no modification to the original byte string was required. - /// - /// Non-ASCII bytes are accepted as valid input. - /// - /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include - /// removing the invalid sequence from the output or passing it as-is. - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } + /// Decodes the next escape sequence, if any, and advances `val`. + #[inline(always)] + fn decode_next<'a>(&self, val: &mut &'a [u8]) -> Option<(&'a [u8], u8)> { + for i in 0..val.len() { + if let (prev, [b'%', p1, p2, rem @ ..]) = val.split_at(i) { + if let Some(ch) = hex_pair_to_char(*p1, *p2) + // ignore protected ascii bytes + .filter(|&ch| !(ch < 128 && self.protected_table.bit_at(ch))) + { + *val = rem; + return Some((prev, ch)); } - } else if ch == b'%' { - has_pct = 1; - - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) } - - idx += 1; } - cloned + None + } + + /// Partially percent-decodes the given bytes. + /// + /// Escape sequences of the protected set are *not* decoded. + /// + /// Returns `None` when no modification to the original bytes was required. + /// + /// Invalid/incomplete percent-encoding sequences are passed unmodified. + pub fn requote(&self, val: &[u8]) -> Option> { + let mut remaining = val; + + // early return indicates that no percent-encoded sequences exist and we can skip allocation + let (pre, decoded_char) = self.decode_next(&mut remaining)?; + + // decoded output will always be shorter than the input + let mut decoded = Vec::::with_capacity(val.len()); + + // push first segment and decoded char + decoded.extend_from_slice(pre); + decoded.push(decoded_char); + + // decode and push rest of segments and decoded chars + while let Some((prev, ch)) = self.decode_next(&mut remaining) { + // this ugly conditional achieves +50% perf in cases where this is a tight loop. + if !prev.is_empty() { + decoded.extend_from_slice(prev); + } + decoded.push(ch); + } + + decoded.extend_from_slice(remaining); + + Some(decoded) } pub(crate) fn requote_str_lossy(&self, val: &str) -> Option { @@ -135,24 +92,6 @@ impl Quoter { } } -/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer -/// representation from `0x0`–`0xF`. -/// -/// - `0x30 ('0') => 0x0` -/// - `0x39 ('9') => 0x9` -/// - `0x41 ('a') => 0xA` -/// - `0x61 ('A') => 0xA` -/// - `0x46 ('f') => 0xF` -/// - `0x66 ('F') => 0xF` -fn from_ascii_hex(v: u8) -> Option { - match v { - b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 - b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 - b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 - _ => None, - } -} - /// Decode a ASCII hex-encoded pair to an integer. /// /// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. @@ -160,64 +99,52 @@ fn from_ascii_hex(v: u8) -> Option { /// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` /// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` /// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` +#[inline(always)] fn hex_pair_to_char(d1: u8, d2: u8) -> Option { - let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); + let d_high = char::from(d1).to_digit(16)?; + let d_low = char::from(d2).to_digit(16)?; // left shift high nibble by 4 bits - Some(d_high << 4 | d_low) + Some((d_high as u8) << 4 | (d_low as u8)) } -/// Sets bit in given bit-map to 1=true. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) +#[derive(Debug, Default, Clone)] +struct AsciiBitmap { + array: [u8; 16], } -/// Returns true if bit to true in given bit-map. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 +impl AsciiBitmap { + /// Sets bit in given bit-map to 1=true. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn set_bit(&mut self, ch: u8) { + self.array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) + } + + /// Returns true if bit to true in given bit-map. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn bit_at(&self, ch: u8) -> bool { + self.array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn hex_encoding() { - let hex = b"0123456789abcdefABCDEF"; - - for i in 0..256 { - let c = i as u8; - if hex.contains(&c) { - assert!(from_ascii_hex(c).is_some()) - } else { - assert!(from_ascii_hex(c).is_none()) - } - } - - let expected = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, - ]; - for i in 0..hex.len() { - assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); - } - } - #[test] fn custom_quoter() { let q = Quoter::new(b"", b"+"); assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc"); + assert_eq!(q.requote(b"/a%2Bc"), None); let q = Quoter::new(b"%+", b"/"); assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb"); + assert_eq!(q.requote(b"/a%2fb"), None); + assert_eq!(q.requote(b"/a%2Fb"), None); assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb"); assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); assert_eq!(q.requote(b"/a\xfe\xffb"), None); @@ -233,7 +160,8 @@ mod tests { #[test] fn invalid_sequences() { let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X"); + assert_eq!(q.requote(b"/a%2x%2X%%"), None); + assert_eq!(q.requote(b"/a%20%2X%%").unwrap(), b"/a %2X%%"); } #[test] diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index e7dda3fca..8ac033861 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -3,7 +3,7 @@ use crate::ResourcePath; use crate::Quoter; thread_local! { - static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); + static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+"); } #[derive(Debug, Clone, Default)] From 2fed9785972f964ecc1a27afca8705c3087aa17c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 28 Mar 2022 22:44:32 +0100 Subject: [PATCH 433/861] remove -http TestRequest doc test --- actix-http/src/test.rs | 24 +----------------------- actix-web/src/test/test_request.rs | 7 ++++--- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 6212c19d1..3815e64c6 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -19,29 +19,7 @@ use crate::{ Request, }; -/// Test `Request` builder -/// -/// ```ignore -/// # use http::{header, StatusCode}; -/// # use actix_web::*; -/// use actix_web::test::TestRequest; -/// -/// fn index(req: &HttpRequest) -> Response { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// Response::Ok().into() -/// } else { -/// Response::BadRequest().into() -/// } -/// } -/// -/// let resp = TestRequest::default().insert_header("content-type", "text/plain") -/// .run(&index) -/// .unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let resp = TestRequest::default().run(&index).unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// ``` +/// Test `Request` builder. pub struct TestRequest(Option); struct Inner { diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index a368d873f..2b60fca71 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -33,7 +33,7 @@ use crate::cookie::{Cookie, CookieJar}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// -/// async fn index(req: HttpRequest) -> HttpResponse { +/// async fn handler(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { @@ -45,10 +45,11 @@ use crate::cookie::{Cookie, CookieJar}; /// # // force rustdoc to display the correct thing and also compile check the test /// # async fn _test() {} /// async fn test_index() { -/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext()) +/// let req = test::TestRequest::default() +/// .insert_header(header::ContentType::plaintext()) /// .to_http_request(); /// -/// let resp = index(req).await; +/// let resp = handler(req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); From de9e41484af51ad47f8a42ff3191f93e565e6875 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Sat, 2 Apr 2022 19:46:26 +0100 Subject: [PATCH 434/861] Add `ServiceRequest::extract` (#2647) Co-authored-by: Rob Ede --- actix-web/CHANGES.md | 3 +++ actix-web/src/extract.rs | 4 +++- actix-web/src/service.rs | 27 ++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 2461cb3a1..ce1837c78 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2021-xx-xx +- Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] + +[#2647]: https://github.com/actix/actix-web/pull/2647 ## 4.0.1 - 2022-02-25 diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index a8b3d4565..1b2f0bd19 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -18,9 +18,11 @@ use crate::{dev::Payload, Error, HttpRequest}; /// A type that implements [`FromRequest`] is called an **extractor** and can extract data from /// the request. Some types that implement this trait are: [`Json`], [`Header`], and [`Path`]. /// +/// Check out [`ServiceRequest::extract`](crate::dev::ServiceRequest::extract) if you want to +/// leverage extractors when implementing middlewares. +/// /// # Configuration /// An extractor can be customized by injecting the corresponding configuration with one of: -/// /// - [`App::app_data()`][crate::App::app_data] /// - [`Scope::app_data()`][crate::Scope::app_data] /// - [`Resource::app_data()`][crate::Resource::app_data] diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 426e9d62b..a9e809bf9 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -24,7 +24,7 @@ use crate::{ guard::{Guard, GuardContext}, info::ConnectionInfo, rmap::ResourceMap, - Error, HttpRequest, HttpResponse, + Error, FromRequest, HttpRequest, HttpResponse, }; pub(crate) type BoxedHttpService = BoxService, Error>; @@ -95,6 +95,31 @@ impl ServiceRequest { (&mut self.req, &mut self.payload) } + /// Derives a type from this request using an [extractor](crate::FromRequest). + /// + /// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy + /// when you want to use an extractor in a middleware implementation. + /// + /// # Examples + /// ``` + /// use actix_web::{ + /// dev::{ServiceRequest, ServiceResponse}, + /// web::Path, Error + /// }; + /// + /// async fn my_helper(mut srv_req: ServiceRequest) -> Result { + /// let path = srv_req.extract::>().await?; + /// // [...] + /// # todo!() + /// } + /// ``` + pub fn extract(&mut self) -> ::Future + where + T: FromRequest, + { + T::from_request(&self.req, &mut self.payload) + } + /// Construct request from parts. pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { #[cfg(debug_assertions)] From 56b9c0d08eec2faec847b6dd5abe5436352cd7b0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 12:31:32 +0100 Subject: [PATCH 435/861] remove payload unwindsafe impl assert --- actix-http/src/h1/payload.rs | 6 ++---- actix-web/src/types/readlines.rs | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 5a93e9051..a8c632396 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,10 +263,8 @@ mod tests { assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); assert_impl_all!(Inner: Unpin, Send, Sync); - #[rustversion::before(1.60)] - assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); - #[rustversion::since(1.60)] - assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); + // assertion not stable wrt rustc versions yet + // assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-web/src/types/readlines.rs b/actix-web/src/types/readlines.rs index 6c456e21c..8a775a073 100644 --- a/actix-web/src/types/readlines.rs +++ b/actix-web/src/types/readlines.rs @@ -20,7 +20,7 @@ use crate::{ /// Stream that reads request line by line. pub struct Readlines { stream: Payload, - buff: BytesMut, + buf: BytesMut, limit: usize, checked_buff: bool, encoding: &'static Encoding, @@ -41,7 +41,7 @@ where Readlines { stream: req.take_payload(), - buff: BytesMut::with_capacity(262_144), + buf: BytesMut::with_capacity(262_144), limit: 262_144, checked_buff: true, err: None, @@ -58,7 +58,7 @@ where fn err(err: ReadlinesError) -> Self { Readlines { stream: Payload::None, - buff: BytesMut::new(), + buf: BytesMut::new(), limit: 262_144, checked_buff: true, encoding: UTF_8, @@ -84,7 +84,7 @@ where // check if there is a newline in the buffer if !this.checked_buff { let mut found: Option = None; - for (ind, b) in this.buff.iter().enumerate() { + for (ind, b) in this.buf.iter().enumerate() { if *b == b'\n' { found = Some(ind); break; @@ -96,13 +96,13 @@ where return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff.split_to(ind + 1)) + str::from_utf8(&this.buf.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { this.encoding .decode_without_bom_handling_and_without_replacement( - &this.buff.split_to(ind + 1), + &this.buf.split_to(ind + 1), ) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? @@ -141,32 +141,32 @@ where .ok_or(ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; - this.buff.extend_from_slice(&bytes); + this.buf.extend_from_slice(&bytes); this.checked_buff = false; return Poll::Ready(Some(Ok(line))); } - this.buff.extend_from_slice(&bytes); + this.buf.extend_from_slice(&bytes); Poll::Pending } None => { - if this.buff.is_empty() { + if this.buf.is_empty() { return Poll::Ready(None); } - if this.buff.len() > this.limit { + if this.buf.len() > this.limit { return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff) + str::from_utf8(&this.buf) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { this.encoding - .decode_without_bom_handling_and_without_replacement(&this.buff) + .decode_without_bom_handling_and_without_replacement(&this.buf) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? }; - this.buff.clear(); + this.buf.clear(); Poll::Ready(Some(Ok(line))) } From f2cacc4c9d3634da5c024bc75315314c97262607 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 13:35:41 +0100 Subject: [PATCH 436/861] clear conn_data on HttpRequest drop (#2742) * clear conn_data on HttpRequest drop fixes #2740 * update changelog * fix doc test --- actix-http/benches/uninit-headers.rs | 2 +- actix-web/CHANGES.md | 5 +++++ actix-web/src/request.rs | 12 +++++++----- actix-web/src/test/test_request.rs | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 5dfd3bc11..3eda96be8 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -114,7 +114,7 @@ mod _original { use std::mem::MaybeUninit; pub fn parse_headers(src: &mut BytesMut) -> usize { - #![allow(clippy::uninit_assumed_init)] + #![allow(invalid_value, clippy::uninit_assumed_init)] let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index ce1837c78..eac40e38d 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,9 +1,14 @@ # Changelog ## Unreleased - 2021-xx-xx +### Added - Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] +### Fixed +- Clear connection-level data on `HttpRequest` drop. [#2742] + [#2647]: https://github.com/actix/actix-web/pull/2647 +[#2742]: https://github.com/actix/actix-web/pull/2742 ## 4.0.1 - 2022-02-25 diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index 5545cf982..d26488e2b 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -381,12 +381,16 @@ impl Drop for HttpRequest { inner.app_data.truncate(1); // Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also - // we know the req_data Rc will not have any cloned at this point to unwrap is okay. + // we know the req_data Rc will not have any clones at this point to unwrap is okay. Rc::get_mut(&mut inner.extensions) .unwrap() .get_mut() .clear(); + // We can't use the same trick as req data because the conn_data is held by the + // dispatcher, too. + inner.conn_data = None; + // a re-borrow of pool is necessary here. let req = Rc::clone(&self.inner); self.app_state().pool().push(req); @@ -761,10 +765,8 @@ mod tests { assert_eq!(body, Bytes::from_static(b"1")); } - // allow deprecated App::data - #[allow(deprecated)] #[actix_rt::test] - async fn test_extensions_dropped() { + async fn test_app_data_dropped() { struct Tracker { pub dropped: bool, } @@ -780,7 +782,7 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let srv = init_service(App::new().data(10u32).service(web::resource("/").to( + let srv = init_service(App::new().service(web::resource("/").to( move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index 2b60fca71..e81561d17 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -53,7 +53,7 @@ use crate::cookie::{Cookie, CookieJar}; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await; +/// let resp = handler(req).await; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From 8abcb9451296f44ef74f111bc29ed099a2bea70d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 14:37:03 +0100 Subject: [PATCH 437/861] fix tokio-uring version --- actix-files/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8f856c109..3eb1edf29 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -40,8 +40,9 @@ percent-encoding = "2.1" pin-project-lite = "0.2.7" # experimental-io-uring -tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } -actix-server = "2.1" # ensure matching tokio-uring versions +[target.'cfg(target_os = "linux")'.dependencies] +tokio-uring = { version = "0.3", optional = true, features = ["bytes"] } +actix-server = { version = "2.1", optional = true } # ensure matching tokio-uring versions [dev-dependencies] actix-rt = "2.7" From 45592b37b60420ee827c964959449d006228290c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 21:01:55 +0100 Subject: [PATCH 438/861] add `Route::wrap` (#2725) * add `Route::wrap` * add tests * fix clippy * fix doctests --- actix-http/benches/uninit-headers.rs | 1 + actix-web/CHANGES.md | 4 +- actix-web/src/route.rs | 83 +++++++++++++++++++++++++--- actix-web/tests/test_server.rs | 54 +++++++++--------- 4 files changed, 108 insertions(+), 34 deletions(-) diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 3eda96be8..688c64d6e 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -119,6 +119,7 @@ mod _original { let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; + #[allow(invalid_value)] let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index eac40e38d..f89189402 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,12 +2,14 @@ ## Unreleased - 2021-xx-xx ### Added -- Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647] +- Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] +- Add `Route::wrap()` to allow individual routes to use middleware. [#2725] ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] [#2647]: https://github.com/actix/actix-web/pull/2647 +[#2725]: https://github.com/actix/actix-web/pull/2725 [#2742]: https://github.com/actix/actix-web/pull/2742 diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs index 0410b99dd..b37128f2c 100644 --- a/actix-web/src/route.rs +++ b/actix-web/src/route.rs @@ -1,15 +1,17 @@ use std::{mem, rc::Rc}; -use actix_http::Method; +use actix_http::{body::MessageBody, Method}; use actix_service::{ + apply, boxed::{self, BoxService}, - fn_service, Service, ServiceFactory, ServiceFactoryExt, + fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use crate::{ guard::{self, Guard}, handler::{handler_service, Handler}, + middleware::Compat, service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, Error, FromRequest, HttpResponse, Responder, }; @@ -35,6 +37,31 @@ impl Route { } } + /// Registers a route middleware. + /// + /// `mw` is a middleware component (type), that can modify the requests and responses handled by + /// this `Route`. + /// + /// See [`App::wrap`](crate::App::wrap) for more details. + #[doc(alias = "middleware")] + #[doc(alias = "use")] // nodejs terminology + pub fn wrap(self, mw: M) -> Route + where + M: Transform< + BoxService, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + B: MessageBody + 'static, + { + Route { + service: boxed::factory(apply(Compat::new(mw), self.service)), + guards: self.guards, + } + } + pub(crate) fn take_guards(&mut self) -> Vec> { mem::take(Rc::get_mut(&mut self.guards).unwrap()) } @@ -246,11 +273,15 @@ mod tests { use futures_core::future::LocalBoxFuture; use serde::Serialize; - use crate::dev::{always_ready, fn_factory, fn_service, Service}; - use crate::http::{header, Method, StatusCode}; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::{ + dev::{always_ready, fn_factory, fn_service, Service}, + error, + http::{header, Method, StatusCode}, + middleware::{DefaultHeaders, Logger}, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, read_body, TestRequest}, + web, App, HttpResponse, + }; #[derive(Serialize, PartialEq, Debug)] struct MyObject { @@ -323,6 +354,44 @@ mod tests { assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } + #[actix_rt::test] + async fn route_middleware() { + let srv = init_service( + App::new() + .route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default())) + .service( + web::resource("/test") + .route(web::get().to(HttpResponse::Ok)) + .route( + web::post() + .to(HttpResponse::Created) + .wrap(DefaultHeaders::new().add(("x-test", "x-posted"))), + ) + .route( + web::delete() + .to(HttpResponse::Accepted) + // logger changes body type, proving Compat is not needed + .wrap(Logger::default()), + ), + ), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + assert!(!res.headers().contains_key("x-test")); + + let req = TestRequest::post().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::CREATED); + assert_eq!(res.headers().get("x-test").unwrap(), "x-posted"); + + let req = TestRequest::delete().uri("/test").to_request(); + let res = call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::ACCEPTED); + } + #[actix_rt::test] async fn test_service_handler() { struct HelloWorld; diff --git a/actix-web/tests/test_server.rs b/actix-web/tests/test_server.rs index bd8934061..270223d69 100644 --- a/actix-web/tests/test_server.rs +++ b/actix-web/tests/test_server.rs @@ -799,34 +799,36 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = Cookie::build("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = Cookie::new("second", "first_value"); + { + let first_cookie = Cookie::build("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = Cookie::new("second", "first_value"); - let cookies = res.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 3); - 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(), 3); + 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(), 3); - 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(), 3); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } } srv.stop().await; From 017e40f73369d79e9beb3191d6fa8f7ac76c4b21 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 21:02:24 +0100 Subject: [PATCH 439/861] update optional extractor impl docs --- actix-web/src/extract.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index 1b2f0bd19..7d187c90a 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -80,9 +80,9 @@ pub trait FromRequest: Sized { } } -/// Optionally extract a field from the request +/// Optionally extract from the request. /// -/// If the FromRequest for T fails, return None rather than returning an error response +/// If the inner `T::from_request` returns an error, handler will receive `None` instead. /// /// # Examples /// ``` @@ -167,9 +167,10 @@ where } } -/// Optionally extract a field from the request or extract the Error if unsuccessful +/// Extract from the request, passing error type through to handler. /// -/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response +/// If the inner `T::from_request` returns an error, allow handler to receive the error rather than +/// immediately returning an error response. /// /// # Examples /// ``` From 9aab9116006b694fc0ac93df26ca2b0dfe6262bc Mon Sep 17 00:00:00 2001 From: Matt Fellenz Date: Sat, 23 Apr 2022 13:57:11 -0700 Subject: [PATCH 440/861] Improve documentation for FromRequest::Future (#2734) Co-authored-by: Rob Ede --- actix-web/src/extract.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index 7d187c90a..d4f5cc91f 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -66,13 +66,29 @@ pub trait FromRequest: Sized { /// The associated error which can be returned. type Error: Into; - /// Future that resolves to a Self. + /// Future that resolves to a `Self`. + /// + /// To use an async function or block, the futures must be boxed. The following snippet will be + /// common when creating async/await extractors (that do not consume the body). + /// + /// ```ignore + /// type Future = Pin>>>; + /// // or + /// type Future = futures_util::future::LocalBoxFuture<'static, Result>; + /// + /// fn from_request(req: HttpRequest, ...) -> Self::Future { + /// let req = req.clone(); + /// Box::pin(async move { + /// ... + /// }) + /// } + /// ``` type Future: Future>; - /// Create a Self from request parts asynchronously. + /// Create a `Self` from request parts asynchronously. fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; - /// Create a Self from request head asynchronously. + /// Create a `Self` from request head asynchronously. /// /// This method is short for `T::from_request(req, &mut Payload::None)`. fn extract(req: &HttpRequest) -> Self::Future { From b1c85ba85be91b5ea34f31264853b411fadce1ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 22:11:45 +0100 Subject: [PATCH 441/861] Add `ServiceConfig::default_service` (#2743) * Add `ServiceConfig::default_service` based on https://github.com/actix/actix-web/pull/2338 * update changelog --- actix-web/CHANGES.md | 7 ++- actix-web/src/app.rs | 8 +++- actix-web/src/config.rs | 96 ++++++++++++++++++++++++++++++++--------- actix-web/src/scope.rs | 4 ++ 4 files changed, 92 insertions(+), 23 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index f89189402..4a16073a6 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,13 +4,16 @@ ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] +- Add `ServiceConfig::default_service()`. [#2338] [#2743] ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] +[#2338]: https://github.com/actix/actix-web/pull/2338 [#2647]: https://github.com/actix/actix-web/pull/2647 [#2725]: https://github.com/actix/actix-web/pull/2725 [#2742]: https://github.com/actix/actix-web/pull/2742 +[#2743]: https://github.com/actix/actix-web/pull/2743 ## 4.0.1 - 2022-02-25 @@ -726,7 +729,7 @@ ### Removed - Public modules `middleware::{normalize, err_handlers}`. All necessary middleware types are now exposed directly by the `middleware` module. -- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 @@ -828,7 +831,7 @@ ## 3.0.0-beta.4 - 2020-09-09 ### Added -- `middleware::NormalizePath` now has configurable behavior for either always having a trailing +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 18749d346..119980a03 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -185,10 +185,17 @@ where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); self.external.extend(cfg.external); self.extensions.extend(cfg.app_data); + + if let Some(default) = cfg.default { + self.default = Some(default); + } + self } @@ -267,7 +274,6 @@ where { let svc = svc .into_factory() - .map(|res| res.map_into_boxed_body()) .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)); self.default = Some(Rc::new(boxed::factory(svc))); diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index 77fba18ed..dab309175 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -1,33 +1,32 @@ -use std::net::SocketAddr; -use std::rc::Rc; +use std::{net::SocketAddr, rc::Rc}; -use actix_http::Extensions; -use actix_router::ResourceDef; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; -use crate::data::Data; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, +use crate::{ + data::Data, + dev::{Extensions, ResourceDef}, + error::Error, + guard::Guard, + resource::Resource, + rmap::ResourceMap, + route::Route, + service::{ + AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, + ServiceRequest, ServiceResponse, + }, }; type Guards = Vec>; -type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { config: AppConfig, root: bool, - default: Rc, + default: Rc, #[allow(clippy::type_complexity)] services: Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -35,7 +34,7 @@ pub struct AppService { impl AppService { /// Crate server settings instance. - pub(crate) fn new(config: AppConfig, default: Rc) -> Self { + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { AppService { config, default, @@ -56,7 +55,7 @@ impl AppService { AppConfig, Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -81,7 +80,7 @@ impl AppService { } /// Returns default handler factory. - pub fn default_service(&self) -> Rc { + pub fn default_service(&self) -> Rc { self.default.clone() } @@ -187,6 +186,7 @@ pub struct ServiceConfig { pub(crate) services: Vec>, pub(crate) external: Vec, pub(crate) app_data: Extensions, + pub(crate) default: Option>, } impl ServiceConfig { @@ -195,6 +195,7 @@ impl ServiceConfig { services: Vec::new(), external: Vec::new(), app_data: Extensions::new(), + default: None, } } @@ -215,6 +216,29 @@ impl ServiceConfig { self } + /// Default service to be used if no matching resource could be found. + /// + /// Counterpart to [`App::default_service()`](crate::App::default_service). + pub fn default_service(&mut self, f: F) -> &mut Self + where + F: IntoServiceFactory, + U: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + > + 'static, + U::InitError: std::fmt::Debug, + { + let svc = f + .into_factory() + .map_init_err(|err| log::error!("Can not construct default service: {:?}", err)); + + self.default = Some(Rc::new(boxed::factory(svc))); + + self + } + /// Run external configuration as part of the application building process /// /// Counterpart to [`App::configure()`](crate::App::configure) that allows for easy nesting. @@ -322,6 +346,38 @@ mod tests { assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } + #[actix_rt::test] + async fn registers_default_service() { + let srv = init_service( + App::new() + .configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("four oh four")), + ); + }) + .service(web::scope("/scoped").configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("scoped four oh four")), + ); + })), + ) + .await; + + // app registers default service + let req = TestRequest::with_uri("/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"four oh four")); + + // scope registers default service + let req = TestRequest::with_uri("/scoped/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"scoped four oh four")); + } + #[actix_rt::test] async fn test_service() { let srv = init_service(App::new().configure(|cfg| { diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index 0fcc83d70..f8c042a5f 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -198,6 +198,10 @@ where .get_or_insert_with(Extensions::new) .extend(cfg.app_data); + if let Some(default) = cfg.default { + self.default = Some(default); + } + self } From 6a5b37020676fdfed4b8c7466d8542904bca825c Mon Sep 17 00:00:00 2001 From: cui fliter Date: Mon, 25 Apr 2022 06:01:20 +0800 Subject: [PATCH 442/861] fix some typos (#2744) Co-authored-by: Rob Ede --- actix-web/Cargo.toml | 2 +- actix-web/src/types/either.rs | 2 +- awc/src/builder.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7793fd8be..702fd6743 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -59,7 +59,7 @@ rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] -# io-uring feature only avaiable for Linux OSes. +# io-uring feature only available for Linux OSes. experimental-io-uring = ["actix-server/io-uring"] [dependencies] diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs index 0eafb9e43..c0faf04b1 100644 --- a/actix-web/src/types/either.rs +++ b/actix-web/src/types/either.rs @@ -49,7 +49,7 @@ use crate::{ /// ``` /// /// # Responder -/// It may be desireable to use a concrete type for a response with multiple branches. As long as +/// It may be desirable to use a concrete type for a response with multiple branches. As long as /// both types implement `Responder`, so will the `Either` type, enabling it to be used as a /// handler's return type. /// diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 16a4e9cb5..c101d18f0 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -165,7 +165,7 @@ where /// Add default header. /// - /// Headers added by this method get added to every request unless overriden by . + /// Headers added by this method get added to every request unless overridden by other methods. /// /// # Panics /// Panics if header name or value is invalid. From dce57a79c96a2c7d5946491df317d9da0d1e1869 Mon Sep 17 00:00:00 2001 From: Sabrina Jewson Date: Mon, 30 May 2022 20:52:48 +0100 Subject: [PATCH 443/861] Implement `ResponseError` for `Infallible` (#2769) --- actix-web/CHANGES.md | 1 + actix-web/src/error/error.rs | 6 ------ actix-web/src/error/response_error.rs | 10 ++++++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 4a16073a6..cb82ef653 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,6 +5,7 @@ - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] - Add `ServiceConfig::default_service()`. [#2338] [#2743] +- Implement `ResponseError` for `std::convert::Infallible` ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] diff --git a/actix-web/src/error/error.rs b/actix-web/src/error/error.rs index 3d3978dde..3a5a128f6 100644 --- a/actix-web/src/error/error.rs +++ b/actix-web/src/error/error.rs @@ -51,12 +51,6 @@ impl StdError for Error { } } -impl From for Error { - fn from(val: std::convert::Infallible) -> Self { - match val {} - } -} - /// `Error` for any error that implements `ResponseError` impl From for Error { fn from(err: T) -> Error { diff --git a/actix-web/src/error/response_error.rs b/actix-web/src/error/response_error.rs index 0b8a82ce8..7d2c06154 100644 --- a/actix-web/src/error/response_error.rs +++ b/actix-web/src/error/response_error.rs @@ -1,6 +1,7 @@ //! `ResponseError` trait and foreign impls. use std::{ + convert::Infallible, error::Error as StdError, fmt, io::{self, Write as _}, @@ -54,6 +55,15 @@ downcast_dyn!(ResponseError); impl ResponseError for Box {} +impl ResponseError for Infallible { + fn status_code(&self) -> StatusCode { + match *self {} + } + fn error_response(&self) -> HttpResponse { + match *self {} + } +} + #[cfg(feature = "openssl")] impl ResponseError for actix_tls::accept::openssl::reexports::Error {} From 8e76a1c77588c4a8214838c7248e7167b13508b1 Mon Sep 17 00:00:00 2001 From: JY Choi Date: Tue, 7 Jun 2022 02:53:23 +0900 Subject: [PATCH 444/861] Allow a path as a guard in route handler macro (#2771) * Allow a path as a guard in route handler macro * Update CHANGES.md Co-authored-by: Rob Ede --- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/src/route.rs | 6 +++--- actix-web-codegen/tests/test_macro.rs | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 8ee787c0a..04bbb4de1 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Fix support for guard paths in route handler macros. [#2771] + +[#2771] https://github.com/actix/actix-web/pull/2771 ## 4.0.0 - 2022-02-24 diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index cb1ba1ef6..cae3cbd55 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -4,7 +4,7 @@ use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta, Path}; enum ResourceType { Async, @@ -77,7 +77,7 @@ impl TryFrom<&syn::LitStr> for MethodType { struct Args { path: syn::LitStr, resource_name: Option, - guards: Vec, + guards: Vec, wrappers: Vec, methods: HashSet, } @@ -121,7 +121,7 @@ impl Args { } } else if nv.path.is_ident("guard") { if let syn::Lit::Str(lit) = nv.lit { - guards.push(Ident::new(&lit.value(), Span::call_site())); + guards.push(lit.parse::()?); } else { return Err(syn::Error::new_spanned( nv.lit, diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 769cf2bc3..55c2417b2 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -96,6 +96,21 @@ async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Resp HttpResponse::Ok() } +mod guard_module { + use actix_web::{guard::GuardContext, http::header}; + + pub fn guard(ctx: &GuardContext) -> bool { + ctx.header::() + .map(|h| h.preference() == "image/*") + .unwrap_or(false) + } +} + +#[get("/test/guard", guard = "guard_module::guard")] +async fn guard_test() -> impl Responder { + HttpResponse::Ok() +} + pub struct ChangeStatusCode; impl Transform for ChangeStatusCode @@ -187,6 +202,7 @@ async fn test_body() { .service(test_handler) .service(route_test) .service(custom_resource_name_test) + .service(guard_test) }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); @@ -245,6 +261,12 @@ async fn test_body() { let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); + + let request = srv + .request(http::Method::GET, srv.url("/test/guard")) + .insert_header(("Accept", "image/*")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } #[actix_rt::test] From 2253eae2bbedfbe3bb5d1d036451a37a6e57e0a6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 04:03:26 +0100 Subject: [PATCH 445/861] update msrv to 1.56 (#2777) * update msrv to 1.56 * remove transitive dashmap dependency closes #2747 --- .github/workflows/ci.yml | 2 +- actix-files/CHANGES.md | 1 + actix-files/README.md | 2 +- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 2 + actix-http/README.md | 2 +- actix-multipart/CHANGES.md | 1 + actix-multipart/README.md | 2 +- actix-router/CHANGES.md | 1 + actix-router/Cargo.toml | 2 - actix-router/examples/flamegraph.rs | 169 ---------------------------- actix-router/src/path.rs | 19 ---- actix-router/src/resource.rs | 65 +++-------- actix-router/src/router.rs | 12 -- actix-test/CHANGES.md | 1 + actix-web-actors/CHANGES.md | 1 + actix-web-actors/README.md | 2 +- actix-web-codegen/CHANGES.md | 1 + actix-web-codegen/README.md | 2 +- actix-web-codegen/tests/trybuild.rs | 2 +- actix-web/CHANGES.md | 3 + actix-web/README.md | 4 +- awc/CHANGES.md | 2 + clippy.toml | 2 +- 25 files changed, 39 insertions(+), 264 deletions(-) delete mode 100644 actix-router/examples/flamegraph.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bb911f79..95dc6ba99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.54.0 # MSRV + - 1.56.0 # MSRV - stable name: ${{ matrix.target.name }} / ${{ matrix.version }} diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7e99c2ae1..7a21e0aba 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,6 +4,7 @@ - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. [#2021]: https://github.com/actix/actix-web/pull/2021 [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/README.md b/actix-files/README.md index 3c4d4443c..5035cad9e 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 3b98e0972..3f0be5356 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.0-beta.13 - 2022-02-16 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index d11ae69b2..8b8cacc2e 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 71132c6b2..75c131512 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.4 - 2022-03-09 diff --git a/actix-http/README.md b/actix-http/README.md index 14a7013db..136582352 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 11ec8a64f..53fbf9393 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 59b9651f1..0b375bf8d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 8e0e4f41e..39ff98c39 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.5.0 - 2022-02-22 diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 76f39f631..ceb5b14dc 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,7 +21,6 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -firestorm = "0.5" http = { version = "0.2.3", optional = true } regex = "1.5" serde = "1" @@ -29,7 +28,6 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-router/examples/flamegraph.rs b/actix-router/examples/flamegraph.rs deleted file mode 100644 index 798cc22d9..000000000 --- a/actix-router/examples/flamegraph.rs +++ /dev/null @@ -1,169 +0,0 @@ -macro_rules! register { - (brackets) => {{ - register!(finish => "{p1}", "{p2}", "{p3}", "{p4}") - }}; - (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{ - let arr = [ - concat!("/authorizations"), - concat!("/authorizations/", $p1), - concat!("/applications/", $p1, "/tokens/", $p2), - concat!("/events"), - concat!("/repos/", $p1, "/", $p2, "/events"), - concat!("/networks/", $p1, "/", $p2, "/events"), - concat!("/orgs/", $p1, "/events"), - concat!("/users/", $p1, "/received_events"), - concat!("/users/", $p1, "/received_events/public"), - concat!("/users/", $p1, "/events"), - concat!("/users/", $p1, "/events/public"), - concat!("/users/", $p1, "/events/orgs/", $p2), - concat!("/feeds"), - concat!("/notifications"), - concat!("/repos/", $p1, "/", $p2, "/notifications"), - concat!("/notifications/threads/", $p1), - concat!("/notifications/threads/", $p1, "/subscription"), - concat!("/repos/", $p1, "/", $p2, "/stargazers"), - concat!("/users/", $p1, "/starred"), - concat!("/user/starred"), - concat!("/user/starred/", $p1, "/", $p2), - concat!("/repos/", $p1, "/", $p2, "/subscribers"), - concat!("/users/", $p1, "/subscriptions"), - concat!("/user/subscriptions"), - concat!("/repos/", $p1, "/", $p2, "/subscription"), - concat!("/user/subscriptions/", $p1, "/", $p2), - concat!("/users/", $p1, "/gists"), - concat!("/gists"), - concat!("/gists/", $p1), - concat!("/gists/", $p1, "/star"), - concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/refs"), - concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3), - concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3), - concat!("/issues"), - concat!("/user/issues"), - concat!("/orgs/", $p1, "/issues"), - concat!("/repos/", $p1, "/", $p2, "/issues"), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3), - concat!("/repos/", $p1, "/", $p2, "/assignees"), - concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"), - concat!("/repos/", $p1, "/", $p2, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/labels/", $p3), - concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"), - concat!("/repos/", $p1, "/", $p2, "/milestones/"), - concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3), - concat!("/emojis"), - concat!("/gitignore/templates"), - concat!("/gitignore/templates/", $p1), - concat!("/meta"), - concat!("/rate_limit"), - concat!("/users/", $p1, "/orgs"), - concat!("/user/orgs"), - concat!("/orgs/", $p1), - concat!("/orgs/", $p1, "/members"), - concat!("/orgs/", $p1, "/members", $p2), - concat!("/orgs/", $p1, "/public_members"), - concat!("/orgs/", $p1, "/public_members/", $p2), - concat!("/orgs/", $p1, "/teams"), - concat!("/teams/", $p1), - concat!("/teams/", $p1, "/members"), - concat!("/teams/", $p1, "/members", $p2), - concat!("/teams/", $p1, "/repos"), - concat!("/teams/", $p1, "/repos/", $p2, "/", $p3), - concat!("/user/teams"), - concat!("/repos/", $p1, "/", $p2, "/pulls"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"), - concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"), - concat!("/user/repos"), - concat!("/users/", $p1, "/repos"), - concat!("/orgs/", $p1, "/repos"), - concat!("/repositories"), - concat!("/repos/", $p1, "/", $p2), - concat!("/repos/", $p1, "/", $p2, "/contributors"), - concat!("/repos/", $p1, "/", $p2, "/languages"), - concat!("/repos/", $p1, "/", $p2, "/teams"), - concat!("/repos/", $p1, "/", $p2, "/tags"), - concat!("/repos/", $p1, "/", $p2, "/branches"), - concat!("/repos/", $p1, "/", $p2, "/branches/", $p3), - concat!("/repos/", $p1, "/", $p2, "/collaborators"), - concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3), - concat!("/repos/", $p1, "/", $p2, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"), - concat!("/repos/", $p1, "/", $p2, "/commits"), - concat!("/repos/", $p1, "/", $p2, "/commits/", $p3), - concat!("/repos/", $p1, "/", $p2, "/readme"), - concat!("/repos/", $p1, "/", $p2, "/keys"), - concat!("/repos/", $p1, "/", $p2, "/keys", $p3), - concat!("/repos/", $p1, "/", $p2, "/downloads"), - concat!("/repos/", $p1, "/", $p2, "/downloads", $p3), - concat!("/repos/", $p1, "/", $p2, "/forks"), - concat!("/repos/", $p1, "/", $p2, "/hooks"), - concat!("/repos/", $p1, "/", $p2, "/hooks", $p3), - concat!("/repos/", $p1, "/", $p2, "/releases"), - concat!("/repos/", $p1, "/", $p2, "/releases/", $p3), - concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"), - concat!("/repos/", $p1, "/", $p2, "/stats/contributors"), - concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"), - concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"), - concat!("/repos/", $p1, "/", $p2, "/stats/participation"), - concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"), - concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3), - concat!("/search/repositories"), - concat!("/search/code"), - concat!("/search/issues"), - concat!("/search/users"), - concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4), - concat!("/legacy/repos/search/", $p1), - concat!("/legacy/user/search/", $p1), - concat!("/legacy/user/email/", $p1), - concat!("/users/", $p1), - concat!("/user"), - concat!("/users"), - concat!("/user/emails"), - concat!("/users/", $p1, "/followers"), - concat!("/user/followers"), - concat!("/users/", $p1, "/following"), - concat!("/user/following"), - concat!("/user/following/", $p1), - concat!("/users/", $p1, "/following", $p2), - concat!("/users/", $p1, "/keys"), - concat!("/user/keys"), - concat!("/user/keys/", $p1), - ]; - - arr.to_vec() - }}; -} - -static PATHS: [&str; 5] = [ - "/authorizations", - "/user/repos", - "/repos/rust-lang/rust/stargazers", - "/orgs/rust-lang/public_members/nikomatsakis", - "/repos/rust-lang/rust/releases/1.51.0", -]; - -fn main() { - let mut router = actix_router::Router::::build(); - - for route in register!(brackets) { - router.path(route, true); - } - - let actix = router.finish(); - - if firestorm::enabled() { - firestorm::bench("target", || { - for &route in &PATHS { - let mut path = actix_router::Path::new(route); - actix.recognize(&mut path).unwrap(); - } - }) - .unwrap(); - } -} diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index dfb645d72..5eef1c1e7 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::ops::{DerefMut, Index}; -use firestorm::profile_method; use serde::de; use crate::{de::PathDeserializer, Resource, ResourcePath}; @@ -52,7 +51,6 @@ impl Path { /// Returns full path as a string. #[inline] pub fn as_str(&self) -> &str { - profile_method!(as_str); self.path.path() } @@ -61,7 +59,6 @@ impl Path { /// Returns empty string if no more is to be processed. #[inline] pub fn unprocessed(&self) -> &str { - profile_method!(unprocessed); // clamp skip to path length let skip = (self.skip as usize).min(self.as_str().len()); &self.path.path()[skip..] @@ -72,8 +69,6 @@ impl Path { #[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")] #[inline] pub fn path(&self) -> &str { - profile_method!(path); - let skip = self.skip as usize; let path = self.path.path(); if skip <= path.len() { @@ -86,8 +81,6 @@ impl Path { /// Set new path. #[inline] pub fn set(&mut self, path: T) { - profile_method!(set); - self.skip = 0; self.path = path; self.segments.clear(); @@ -96,8 +89,6 @@ impl Path { /// Reset state. #[inline] pub fn reset(&mut self) { - profile_method!(reset); - self.skip = 0; self.segments.clear(); } @@ -105,13 +96,10 @@ impl Path { /// Skip first `n` chars in path. #[inline] pub fn skip(&mut self, n: u16) { - profile_method!(skip); self.skip += n; } pub(crate) fn add(&mut self, name: impl Into>, value: PathItem) { - profile_method!(add); - match value { PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))), PathItem::Segment(begin, end) => self.segments.push(( @@ -127,8 +115,6 @@ impl Path { name: impl Into>, value: impl Into>, ) { - profile_method!(add_static); - self.segments .push((name.into(), PathItem::Static(value.into()))); } @@ -147,8 +133,6 @@ impl Path { /// Get matched parameter by name without type conversion pub fn get(&self, name: &str) -> Option<&str> { - profile_method!(get); - for (seg_name, val) in self.segments.iter() { if name == seg_name { return match val { @@ -167,8 +151,6 @@ impl Path { /// /// If keyed parameter is not available empty string is used as default value. pub fn query(&self, key: &str) -> &str { - profile_method!(query); - if let Some(s) = self.get(key) { s } else { @@ -186,7 +168,6 @@ impl Path { /// Try to deserialize matching parameters to a specified type `U` pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result { - profile_method!(load); de::Deserialize::deserialize(PathDeserializer::new(self)) } } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 3d121f369..bc082273c 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -5,7 +5,6 @@ use std::{ mem, }; -use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; use tracing::error; @@ -272,7 +271,6 @@ impl ResourceDef { /// assert!(!resource.is_match("/foo")); /// ``` pub fn new(paths: T) -> Self { - profile_method!(new); Self::construct(paths, false) } @@ -300,7 +298,6 @@ impl ResourceDef { /// assert!(!resource.is_match("/foo")); /// ``` pub fn prefix(paths: T) -> Self { - profile_method!(prefix); ResourceDef::construct(paths, true) } @@ -325,7 +322,6 @@ impl ResourceDef { /// assert!(!resource.is_match("user/123")); /// ``` pub fn root_prefix(path: &str) -> Self { - profile_method!(root_prefix); ResourceDef::prefix(insert_slash(path).into_owned()) } @@ -549,8 +545,6 @@ impl ResourceDef { /// ``` #[inline] pub fn is_match(&self, path: &str) -> bool { - profile_method!(is_match); - // this function could be expressed as: // `self.find_match(path).is_some()` // but this skips some checks and uses potentially faster regex methods @@ -598,8 +592,6 @@ impl ResourceDef { /// assert_eq!(resource.find_match("/profile/1234"), Some(13)); /// ``` pub fn find_match(&self, path: &str) -> Option { - profile_method!(find_match); - match &self.pat_type { PatternType::Static(pattern) => self.static_match(pattern, path), @@ -634,7 +626,6 @@ impl ResourceDef { /// assert_eq!(path.unprocessed(), ""); /// ``` pub fn capture_match_info(&self, resource: &mut R) -> bool { - profile_method!(capture_match_info); self.capture_match_info_fn(resource, |_| true) } @@ -680,53 +671,35 @@ impl ResourceDef { R: Resource, F: FnOnce(&R) -> bool, { - profile_method!(capture_match_info_fn); - let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default(); let path = resource.resource_path(); let path_str = path.unprocessed(); let (matched_len, matched_vars) = match &self.pat_type { - PatternType::Static(pattern) => { - profile_section!(pattern_static_or_prefix); - - match self.static_match(pattern, path_str) { - Some(len) => (len, None), - None => return false, - } - } + PatternType::Static(pattern) => match self.static_match(pattern, path_str) { + Some(len) => (len, None), + None => return false, + }, PatternType::Dynamic(re, names) => { - profile_section!(pattern_dynamic); - - let captures = { - profile_section!(pattern_dynamic_regex_exec); - - match re.captures(path.unprocessed()) { - Some(captures) => captures, - _ => return false, - } + let captures = match re.captures(path.unprocessed()) { + Some(captures) => captures, + _ => return false, }; - { - profile_section!(pattern_dynamic_extract_captures); - - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(name) { - segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - error!("Dynamic path match but not all segments found: {}", name); - return false; - } + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); + } else { + error!("Dynamic path match but not all segments found: {}", name); + return false; } - }; + } (captures[1].len(), Some(names)) } PatternType::DynamicSet(re, params) => { - profile_section!(pattern_dynamic_set); - let path = path.unprocessed(); let (pattern, names) = match re.matches(path).into_iter().next() { Some(idx) => ¶ms[idx], @@ -809,7 +782,6 @@ impl ResourceDef { I: IntoIterator, I::Item: AsRef, { - profile_method!(resource_path_from_iter); let mut iter = values.into_iter(); self.build_resource_path(path, |_| iter.next()) } @@ -845,7 +817,6 @@ impl ResourceDef { V: AsRef, S: BuildHasher, { - profile_method!(resource_path_from_map); self.build_resource_path(path, |name| values.get(name)) } @@ -866,8 +837,6 @@ impl ResourceDef { } fn construct(paths: T, is_prefix: bool) -> Self { - profile_method!(construct); - let patterns = paths.patterns(); let (pat_type, segments) = match &patterns { Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false), @@ -926,8 +895,6 @@ impl ResourceDef { /// # Panics /// Panics if given patterns does not contain a dynamic segment. fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) { - profile_method!(parse_param); - const DEFAULT_PATTERN: &str = "[^/]+"; const DEFAULT_PATTERN_TAIL: &str = ".*"; @@ -997,8 +964,6 @@ impl ResourceDef { is_prefix: bool, force_dynamic: bool, ) -> (PatternType, Vec) { - profile_method!(parse); - if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') { // pattern is static return ( @@ -1131,8 +1096,6 @@ impl From for ResourceDef { } pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> { - profile_fn!(insert_slash); - if !path.is_empty() && !path.starts_with('/') { let mut new_path = String::with_capacity(path.len() + 1); new_path.push('/'); diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index f0e598683..8ed966b59 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -1,5 +1,3 @@ -use firestorm::profile_method; - use crate::{IntoPatterns, Resource, ResourceDef}; #[derive(Debug, Copy, Clone, PartialEq)] @@ -30,7 +28,6 @@ impl Router { where R: Resource, { - profile_method!(recognize); self.recognize_fn(resource, |_, _| true) } @@ -39,7 +36,6 @@ impl Router { where R: Resource, { - profile_method!(recognize_mut); self.recognize_mut_fn(resource, |_, _| true) } @@ -55,8 +51,6 @@ impl Router { R: Resource, F: FnMut(&R, &U) -> bool, { - profile_method!(recognize_checked); - for (rdef, val, ctx) in self.routes.iter() { if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { return Some((val, ResourceId(rdef.id()))); @@ -77,8 +71,6 @@ impl Router { R: Resource, F: FnMut(&R, &U) -> bool, { - profile_method!(recognize_mut_checked); - for (rdef, val, ctx) in self.routes.iter_mut() { if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { return Some((val, ResourceId(rdef.id()))); @@ -104,7 +96,6 @@ impl RouterBuilder { val: T, ctx: U, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(push); self.routes.push((rdef, val, ctx)); self.routes .last_mut() @@ -131,7 +122,6 @@ where path: impl IntoPatterns, val: T, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(path); self.push(ResourceDef::new(path), val, U::default()) } @@ -141,13 +131,11 @@ where prefix: impl IntoPatterns, val: T, ) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(prefix); self.push(ResourceDef::prefix(prefix), val, U::default()) } /// Registers resource for [`ResourceDef`]. pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) { - profile_method!(rdef); self.push(rdef, val, U::default()) } } diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 13e75c01a..9b84f04b0 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 0.1.0-beta.13 - 2022-02-16 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index b4844bfa6..f143be29c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 4.1.0 - 2022-03-02 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 357154a86..39a10a4e2 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 04bbb4de1..14b368064 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2021-xx-xx - Fix support for guard paths in route handler macros. [#2771] +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. [#2771] https://github.com/actix/actix-web/pull/2771 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 439beadb4..178bb8c67 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.0)](https://docs.rs/actix-web-codegen/4.0.0) -[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) +![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.0) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index b2d9ce186..13eb84559 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.54)] // MSRV +#[rustversion::stable(1.56)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index cb82ef653..86ded5729 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -7,6 +7,9 @@ - Add `ServiceConfig::default_service()`. [#2338] [#2743] - Implement `ResponseError` for `std::convert::Infallible` +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. + ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] diff --git a/actix-web/README.md b/actix-web/README.md index 957fb47b8..1eaaa2049 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1) -![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) +![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1)
@@ -33,7 +33,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.54+ +- Runs on stable Rust 1.56+ ## Documentation diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ebc0dbe61..622388286 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ## 3.0.0 - 2022-03-07 diff --git a/clippy.toml b/clippy.toml index ece14b8d2..62ca74234 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.54" +msrv = "1.56" From 498fb954b37f4439e1e88ccc5606ec8e95fc974b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 04:53:58 +0100 Subject: [PATCH 446/861] migrate from deprecated sha-1 to sha1 (#2780) closes #2778 --- actix-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cd5d3f379..8bdd72799 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -37,7 +37,7 @@ ws = [ "local-channel", "base64", "rand", - "sha-1", + "sha1", ] # TLS via OpenSSL @@ -86,7 +86,7 @@ h2 = { version = "0.3.9", optional = true } local-channel = { version = "0.1", optional = true } base64 = { version = "0.13", optional = true } rand = { version = "0.8", optional = true } -sha-1 = { version = "0.10", optional = true } +sha1 = { version = "0.10", optional = true } # openssl/rustls actix-tls = { version = "3", default-features = false, optional = true } From 264a703d947d4eaa3b39f8e90d02bb7800ddea54 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 13:43:13 +0100 Subject: [PATCH 447/861] revert broken fix in #2624 (#2779) * revert broken fix in #2624 * update changelog --- actix-http/CHANGES.md | 5 ++ actix-http/src/h1/dispatcher.rs | 73 ++------------------------- actix-http/src/h1/dispatcher_tests.rs | 3 ++ 3 files changed, 12 insertions(+), 69 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 75c131512..d431ec5f3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,11 @@ ### Changed - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +### Fixed +- Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] + +[#2779]: https://github.com/actix/actix-web/issues/2779 + ## 3.0.4 - 2022-03-09 ### Fixed diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index dea8a4beb..c2ddc06ba 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,14 +15,14 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; -use tracing::{debug, error, trace}; +use tracing::{error, trace}; use crate::{ body::{BodySize, BoxBody, MessageBody}, config::ServiceConfig, error::{DispatchError, ParseError, PayloadError}, service::HttpFlow, - ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode, + Error, Extensions, OnConnectData, Request, Response, StatusCode, }; use super::{ @@ -691,74 +691,12 @@ where let can_not_read = !self.can_read(cx); // limit amount of non-processed requests - if pipeline_queue_full { + if pipeline_queue_full || can_not_read { return Ok(false); } let mut this = self.as_mut().project(); - if can_not_read { - debug!("cannot read request payload"); - - if let Some(sender) = &this.payload { - // ...maybe handler does not want to read any more payload... - if let PayloadStatus::Dropped = sender.need_read(cx) { - debug!("handler dropped payload early; attempt to clean connection"); - // ...in which case poll request payload a few times - loop { - match this.codec.decode(this.read_buf)? { - Some(msg) => { - match msg { - // payload decoded did not yield EOF yet - Message::Chunk(Some(_)) => { - // if non-clean connection, next loop iter will detect empty - // read buffer and close connection - } - - // connection is in clean state for next request - Message::Chunk(None) => { - debug!("connection successfully cleaned"); - - // reset dispatcher state - let _ = this.payload.take(); - this.state.set(State::None); - - // break out of payload decode loop - break; - } - - // Either whole payload is read and loop is broken or more data - // was expected in which case connection is closed. In both - // situations dispatcher cannot get here. - Message::Item(_) => { - unreachable!("dispatcher is in payload receive state") - } - } - } - - // not enough info to decide if connection is going to be clean or not - None => { - error!( - "handler did not read whole payload and dispatcher could not \ - drain read buf; return 500 and close connection" - ); - - this.flags.insert(Flags::SHUTDOWN); - let mut res = Response::internal_server_error().drop_body(); - res.head_mut().set_connection_type(ConnectionType::Close); - this.messages.push_back(DispatcherMessage::Error(res)); - *this.error = Some(DispatchError::HandlerDroppedPayload); - return Ok(true); - } - } - } - } - } else { - // can_not_read and no request payload - return Ok(false); - } - } - let mut updated = false; // decode from read buf as many full requests as possible @@ -904,10 +842,7 @@ where if timer.as_mut().poll(cx).is_ready() { // timeout on first request (slow request) return 408 - trace!( - "timed out on slow request; \ - replying with 408 and closing connection" - ); + trace!("timed out on slow request; replying with 408 and closing connection"); let _ = self.as_mut().send_error_response( Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 40454d45a..b3ee3d2bb 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -783,6 +783,9 @@ async fn upgrade_handling() { .await; } +// fix in #2624 reverted temporarily +// complete fix tracked in #2745 +#[ignore] #[actix_rt::test] async fn handler_drop_payload() { let _ = env_logger::try_init(); From 43671ae4aaf2c20150fd1e5ca54055eb5d114273 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Jun 2022 16:15:43 +0100 Subject: [PATCH 448/861] release 4.1 group (#2781) --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 4 ++-- actix-files/README.md | 4 ++-- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 4 ++-- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 5 ++++- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- 13 files changed, 30 insertions(+), 16 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7a21e0aba..ff3ec13ac 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.1 - 2022-06-11 - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3eb1edf29..02543095f 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0" +version = "0.6.1" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -47,5 +47,5 @@ actix-server = { version = "2.1", optional = true } # ensure matching tokio-urin [dev-dependencies] actix-rt = "2.7" actix-test = "0.1.0-beta.13" -actix-web = "4.0.0" +actix-web = "4" tempfile = "3.2" diff --git a/actix-files/README.md b/actix-files/README.md index 5035cad9e..737c0edef 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0) +[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d431ec5f3..980997a06 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,12 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.1.0 - 2022-06-11 ### Changed - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. ### Fixed - Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] +[#2357]: https://github.com/actix/actix-web/issues/2357 +[#2624]: https://github.com/actix/actix-web/issues/2624 [#2779]: https://github.com/actix/actix-web/issues/2779 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8bdd72799..2a4966884 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.4" +version = "3.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = "4.0.0" +actix-web = "4" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-http/README.md b/actix-http/README.md index 136582352..388761aee 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) +[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e93e22941..507e92752 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -15,7 +15,7 @@ path = "src/lib.rs" [dependencies] actix-utils = "3" -actix-web = { version = "4.0.0", default-features = false } +actix-web = { version = "4", default-features = false } bytes = "1" derive_more = "0.99.5" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 14b368064..24ab212a8 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,10 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.1 - 2022-06-11 - Fix support for guard paths in route handler macros. [#2771] - Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. -[#2771] https://github.com/actix/actix-web/pull/2771 +[#2771]: https://github.com/actix/actix-web/pull/2771 ## 4.0.0 - 2022-02-24 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 0d8b86459..52094443b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "4.0.0" +version = "4.0.1" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 178bb8c67..f02e8eb35 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.0)](https://docs.rs/actix-web-codegen/4.0.0) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) ![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.0) +[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 86ded5729..307bba9ba 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2021-xx-xx + + +## 4.1.0 - 2022-06-11 ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 702fd6743..8cdf0f611 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.1" +version = "4.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index 1eaaa2049..65ee4efca 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@
+3.0.0 Pre-Releases + ## 3.0.0-beta.13 - 2022-02-16 - No significant changes since `3.0.0-beta.12`. @@ -69,6 +84,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 +
## 2.1.0 - 2020-11-25 - Add ability to set address for `TestServer`. [#1645] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6f7563ffa..0a9ddf947 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.13" +version = "3.0.0" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 9429bb760..ec2bd769c 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0)](https://docs.rs/actix-http-test/3.0.0) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 03767ca4e..ba8c80e64 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.11", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } +actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } actix-web = "4" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 9938be67d..224de7493 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" actix-http = "3" -actix-http-test = "3.0.0-beta.13" +actix-http-test = "3" actix-rt = "2.1" actix-service = "2" actix-utils = "3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9da103cb0..49733bcea 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,7 +94,7 @@ trust-dns-resolver = { version = "0.21", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } +actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } From d78ff283af6b7fa244c7b67dae994c3ca0169fe1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 24 Jul 2022 02:13:46 +0100 Subject: [PATCH 489/861] prepare actix-test release 0.1.0 --- actix-files/Cargo.toml | 2 +- actix-test/CHANGES.md | 3 +++ actix-test/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 5559d1b7e..30356d81a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -46,6 +46,6 @@ actix-server = { version = "2.1", optional = true } # ensure matching tokio-urin [dev-dependencies] actix-rt = "2.7" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" actix-web = "4" tempfile = "3.2" diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 43e306bb1..bf5d9324f 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 0.1.0 - 2022-07-24 - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 224de7493..eaea15d47 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.13" +version = "0.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 284351ed3..8222fc864 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" awc = { version = "3", default-features = false } actix-web = { version = "4", features = ["macros"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a2aac7e68..0c3b70589 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ syn = { version = "1", features = ["full", "extra-traits"] } [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" actix-utils = "3.0.0" actix-web = "4.0.0" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index c96f3c1bb..12806e686 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -102,7 +102,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6" -actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } +actix-test = { version = "0.1", features = ["openssl", "rustls"] } awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 49733bcea..0250091bf 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -96,7 +96,7 @@ trust-dns-resolver = { version = "0.21", optional = true } actix-http = { version = "3", features = ["openssl"] } actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" -actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } +actix-test = { version = "0.1", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-utils = "3" actix-web = { version = "4", features = ["openssl"] } From e0a88cea8d741da580c00dc56f268f615cd581ce Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 24 Jul 2022 02:47:12 +0100 Subject: [PATCH 490/861] remove unwindsafe assertions --- actix-http/src/h1/payload.rs | 6 +----- actix-http/src/h2/mod.rs | 4 +--- actix-http/src/payload.rs | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index d6ffa662e..1ed785a1b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -252,19 +252,15 @@ impl Inner { #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use actix_utils::future::poll_fn; use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; assert_impl_all!(Payload: Unpin); - assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + assert_not_impl_any!(Payload: Send, Sync); assert_impl_all!(Inner: Unpin, Send, Sync); - // assertion not stable wrt rustc versions yet - // assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index c8aaaaa5f..39198e0fe 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -103,11 +103,9 @@ where #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use static_assertions::assert_impl_all; use super::*; - assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe); + assert_impl_all!(Payload: Unpin, Send, Sync); } diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index ee0128af4..7d476c55f 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -97,12 +97,10 @@ where #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; assert_impl_all!(Payload: Unpin); - assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + assert_not_impl_any!(Payload: Send, Sync); } From 8ff489aa90d70cdf8cecee0873f736c3e38f3521 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 24 Jul 2022 16:35:00 +0100 Subject: [PATCH 491/861] apply fix from #2369 --- actix-http/CHANGES.md | 12 ++++++++---- actix-http/src/h1/dispatcher.rs | 11 +++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7e6604046..785a1b13f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Avoid possibility of dispatcher getting stuck while back-pressuring I/O. [#2369] + +[#2369]: https://github.com/actix/actix-web/pull/2369 ## 3.2.1 - 2022-07-02 @@ -29,9 +33,9 @@ ### Fixed - Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] +[#2624]: https://github.com/actix/actix-web/pull/2624 [#2357]: https://github.com/actix/actix-web/issues/2357 -[#2624]: https://github.com/actix/actix-web/issues/2624 -[#2779]: https://github.com/actix/actix-web/issues/2779 +[#2779]: https://github.com/actix/actix-web/pull/2779 ## 3.0.4 - 2022-03-09 @@ -43,14 +47,14 @@ ### Fixed - Allow spaces between header name and colon when parsing responses. [#2684] -[#2684]: https://github.com/actix/actix-web/issues/2684 +[#2684]: https://github.com/actix/actix-web/pull/2684 ## 3.0.2 - 2022-03-05 ### Fixed - Fix encoding camel-case header names with more than one hyphen. [#2683] -[#2683]: https://github.com/actix/actix-web/issues/2683 +[#2683]: https://github.com/actix/actix-web/pull/2683 ## 3.0.1 - 2022-03-04 diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c2ddc06ba..81090667d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -976,9 +976,11 @@ where // // A Request head too large to parse is only checked on `httparse::Status::Partial`. - if this.payload.is_none() { - // When dispatcher has a payload the responsibility of wake up it would be shift - // to h1::payload::Payload. + match this.payload { + // When dispatcher has a payload the responsibility of wake ups is shifted to + // `h1::payload::Payload` unless the payload is needing a read, in which case it + // might not have access to the waker and could result in the dispatcher + // getting stuck until timeout. // // Reason: // Self wake up when there is payload would waste poll and/or result in @@ -989,7 +991,8 @@ where // read anymore. At this case read_buf could always remain beyond // MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and // waste resources. - cx.waker().wake_by_ref(); + Some(ref p) if p.need_read(cx) != PayloadStatus::Read => {} + _ => cx.waker().wake_by_ref(), } return Ok(false); From 4bbe60b609ef1d66475b26fab9476a2fbcf6fc45 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 24 Jul 2022 16:42:35 +0100 Subject: [PATCH 492/861] document h2 ping-pong --- actix-http/src/config.rs | 2 +- actix-http/src/h2/dispatcher.rs | 39 +++++++++++++++++++-------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index ac95a2802..c0d297a20 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -35,7 +35,7 @@ impl Default for ServiceConfig { } impl ServiceConfig { - /// Create instance of `ServiceConfig` + /// Create instance of `ServiceConfig`. pub fn new( keep_alive: KeepAlive, client_request_timeout: Duration, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 85516cccc..680936f0f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -67,7 +67,7 @@ where timer }) .unwrap_or_else(|| Box::pin(sleep(dur))), - on_flight: false, + in_flight: false, ping_pong: conn.ping_pong().unwrap(), }); @@ -84,9 +84,14 @@ where } struct H2PingPong { - timer: Pin>, - on_flight: bool, + /// Handle to send ping frames from the peer. ping_pong: PingPong, + + /// True when a ping has been sent and is waiting for a reply. + in_flight: bool, + + /// Timeout for pong response. + timer: Pin>, } impl Future for Dispatcher @@ -152,26 +157,28 @@ where }); } Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => match this.ping_pong.as_mut() { Some(ping_pong) => loop { - if ping_pong.on_flight { - // When have on flight ping pong. poll pong and and keep alive timer. - // on success pong received update keep alive timer to determine the next timing of - // ping pong. + if ping_pong.in_flight { + // When there is an in-flight ping-pong, poll pong and and keep-alive + // timer. On successful pong received, update keep-alive timer to + // determine the next timing of ping pong. match ping_pong.ping_pong.poll_pong(cx)? { Poll::Ready(_) => { - ping_pong.on_flight = false; + ping_pong.in_flight = false; let dead_line = this.config.keep_alive_deadline().unwrap(); ping_pong.timer.as_mut().reset(dead_line.into()); } Poll::Pending => { - return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) + return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())); } } } else { - // When there is no on flight ping pong. keep alive timer is used to wait for next - // timing of ping pong. Therefore at this point it serves as an interval instead. + // When there is no in-flight ping-pong, keep-alive timer is used to + // wait for next timing of ping-pong. Therefore, at this point it serves + // as an interval instead. ready!(ping_pong.timer.as_mut().poll(cx)); ping_pong.ping_pong.send_ping(Ping::opaque())?; @@ -179,7 +186,7 @@ where let dead_line = this.config.keep_alive_deadline().unwrap(); ping_pong.timer.as_mut().reset(dead_line.into()); - ping_pong.on_flight = true; + ping_pong.in_flight = true; } }, None => return Poll::Pending, @@ -287,13 +294,13 @@ fn prepare_response( _ => {} } - let _ = match size { - BodySize::None | BodySize::Stream => None, + match size { + BodySize::None | BodySize::Stream => {} BodySize::Sized(0) => { #[allow(clippy::declare_interior_mutable_const)] const HV_ZERO: HeaderValue = HeaderValue::from_static("0"); - res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO) + res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO); } BodySize::Sized(len) => { @@ -302,7 +309,7 @@ fn prepare_response( res.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(buf.format(*len)).unwrap(), - ) + ); } }; From 10746fb2fbbc87acf80e16afa426322762366ab7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 31 Jul 2022 21:58:15 +0100 Subject: [PATCH 493/861] improve HttpServer docs --- actix-web/src/server.rs | 209 +++++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 87 deletions(-) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 9c5076d36..2297dfa98 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -41,17 +41,22 @@ struct Config { /// /// Create new HTTP server with application factory. /// +/// # HTTP/2 +/// Currently, HTTP/2 is only supported when using TLS (HTTPS). See `bind_rustls` or `bind_openssl`. +/// +/// # Examples /// ```no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; /// -/// #[actix_rt::main] +/// #[actix_web::main] /// async fn main() -> std::io::Result<()> { -/// HttpServer::new( -/// || App::new() -/// .service(web::resource("/").to(|| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090")? -/// .run() -/// .await +/// HttpServer::new(|| { +/// App::new() +/// .service(web::resource("/").to(|| async { "hello world" })) +/// }) +/// .bind(("127.0.0.1", 8080))? +/// .run() +/// .await /// } /// ``` pub struct HttpServer @@ -133,7 +138,7 @@ where } } - /// Set number of workers to start. + /// Sets number of workers to start (per bind address). /// /// By default, the number of available physical CPUs is used as the worker count. pub fn workers(mut self, num: usize) -> Self { @@ -141,7 +146,7 @@ where self } - /// Set the maximum number of pending connections. + /// Sets the maximum number of pending connections. /// /// This refers to the number of clients that can be waiting to be served. /// Exceeding this number results in the client getting an error when @@ -157,7 +162,7 @@ where self } - /// Sets the maximum per-worker number of concurrent connections. + /// Sets the per-worker maximum number of concurrent connections. /// /// All socket listeners will stop accepting connections when this limit is reached for /// each worker. @@ -168,7 +173,7 @@ where self } - /// Sets the maximum per-worker concurrent connection establish process. + /// Sets the per-worker maximum concurrent TLS connection limit. /// /// All listeners will stop accepting connections when this limit is reached. It can be used to /// limit the global TLS CPU usage. @@ -181,7 +186,7 @@ where self } - /// Set max number of threads for each worker's blocking task thread pool. + /// Sets max number of threads for each worker's blocking task thread pool. /// /// One thread pool is set up **per worker**; not shared across workers. /// @@ -191,7 +196,7 @@ where self } - /// Set server keep-alive setting. + /// Sets server keep-alive preference. /// /// By default keep alive is set to a 5 seconds. pub fn keep_alive>(self, val: T) -> Self { @@ -199,11 +204,10 @@ where self } - /// Set server client timeout in milliseconds for first request. + /// Sets server client timeout for first request. /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. + /// Defines a timeout for reading client request head. If a client does not transmit the entire + /// set headers within this time, the request is terminated with a 408 (Request Timeout) error. /// /// To disable timeout set value to 0. /// @@ -219,10 +223,10 @@ where self.client_request_timeout(dur) } - /// Set server connection shutdown timeout in milliseconds. + /// Sets server connection shutdown timeout. /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. + /// Defines a timeout for connection shutdown. If a shutdown procedure does not complete within + /// this time, the request is dropped. /// /// To disable timeout set value to 0. /// @@ -232,10 +236,10 @@ where self } - /// Set TLS handshake timeout. + /// Sets TLS handshake timeout. /// - /// Defines a timeout for TLS handshake. If the TLS handshake does not complete - /// within this time, the connection is closed. + /// Defines a timeout for TLS handshake. If the TLS handshake does not complete within this + /// time, the connection is closed. /// /// By default handshake timeout is set to 3000 milliseconds. #[cfg(any(feature = "openssl", feature = "rustls"))] @@ -256,35 +260,35 @@ where self.client_disconnect_timeout(Duration::from_millis(dur)) } - /// Set server host name. + /// Sets server host name. /// - /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) - /// documentation for more information. + /// Host name is used by application router as a hostname for url generation. Check + /// [`ConnectionInfo`](crate::dev::ConnectionInfo::host()) docs for more info. /// - /// By default host name is set to a "localhost" value. + /// By default, hostname is set to "localhost". pub fn server_hostname>(self, val: T) -> Self { self.config.lock().unwrap().host = Some(val.as_ref().to_owned()); self } - /// Stop Actix `System` after server shutdown. + /// Stops `actix_rt` `System` after server shutdown. + /// + /// Does nothing when running under `#[tokio::main]` runtime. pub fn system_exit(mut self) -> Self { self.builder = self.builder.system_exit(); self } - /// Disable signal handling + /// Disables signal handling. pub fn disable_signals(mut self) -> Self { self.builder = self.builder.disable_signals(); self } - /// Timeout for graceful workers shutdown. + /// Sets timeout for graceful worker shutdown of workers. /// - /// After receiving a stop signal, workers have this much time to finish - /// serving requests. Workers still alive after the timeout are force - /// dropped. + /// After receiving a stop signal, workers have this much time to finish serving requests. + /// Workers still alive after the timeout are force dropped. /// /// By default shutdown timeout sets to 30 seconds. pub fn shutdown_timeout(mut self, sec: u64) -> Self { @@ -292,33 +296,34 @@ where self } - /// Get addresses of bound sockets. + /// Returns addresses of bound sockets. pub fn addrs(&self) -> Vec { self.sockets.iter().map(|s| s.addr).collect() } - /// Get addresses of bound sockets and the scheme for it. + /// Returns addresses of bound sockets and the scheme for it. /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on HTTP and some listening on HTTPS - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. + /// This is useful when the server is bound from different sources with some sockets listening + /// on HTTP and some listening on HTTPS and the user should be presented with an enumeration of + /// which socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() } - /// Use listener for accepting incoming connection requests + /// Binds to existing listener for accepting incoming connection requests. /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. + /// No changes are made to `lst`'s configuration. Ensure it is configured properly before + /// passing ownership to `listen()`. pub fn listen(mut self, lst: net::TcpListener) -> io::Result { let cfg = self.config.clone(); let factory = self.factory.clone(); let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { addr, scheme: "http", }); + let on_connect_fn = self.on_connect_fn.clone(); self.builder = @@ -351,20 +356,23 @@ where Ok(self) } - #[cfg(feature = "openssl")] - /// Use listener for accepting incoming tls connection requests + /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. /// - /// This method sets alpn protocols to "h2" and "http/1.1" + /// See [listen()](Self::listen) for more details on the `lst` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "openssl")] + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn listen_openssl( self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { - self.listen_ssl_inner(lst, openssl_acceptor(builder)?) + self.listen_openssl_inner(lst, openssl_acceptor(builder)?) } #[cfg(feature = "openssl")] - fn listen_ssl_inner( + fn listen_openssl_inner( mut self, lst: net::TcpListener, acceptor: SslAcceptor, @@ -417,10 +425,13 @@ where Ok(self) } - #[cfg(feature = "rustls")] - /// Use listener for accepting incoming tls connection requests + /// Binds to existing listening for accepting incoming TLS connection requests using Rustls. /// - /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones + /// See [listen()](Self::listen) for more details on the `lst` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn listen_rustls( self, lst: net::TcpListener, @@ -480,11 +491,36 @@ where Ok(self) } - /// The socket address to bind + /// Resolves socket address(es) and binds server to created listener(s). /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: A) -> io::Result { - let sockets = self.bind2(addr)?; + /// # Hostname Resolution + /// When `addr` includes a hostname, it is possible for this method to bind to both the IPv4 and + /// IPv6 addresses that result from a DNS lookup. You can test this by passing `localhost:8080` + /// and noting that the server binds to `127.0.0.1:8080` _and_ `[::1]:8080`. To bind additional + /// addresses, call this method multiple times. + /// + /// Note that, if a DNS lookup is required, resolving hostnames is a blocking operation. + /// + /// # Typical Usage + /// In general, use `127.0.0.1:` when testing locally and `0.0.0.0:` when deploying + /// (with or without a reverse proxy or load balancer) so that the server is accessible. + /// + /// # Errors + /// Returns an `io::Error` if: + /// - `addrs` cannot be resolved into one or more socket addresses; + /// - all the resolved socket addresses are already bound. + /// + /// # Example + /// ``` + /// # use actix_web::{App, HttpServer}; + /// # fn inner() -> std::io::Result<()> { + /// HttpServer::new(|| App::new()) + /// .bind(("127.0.0.1", 8080))? + /// .bind("[::1]:9000")? + /// # ; Ok(()) } + /// ``` + pub fn bind(mut self, addrs: A) -> io::Result { + let sockets = self.bind2(addrs)?; for lst in sockets { self = self.listen(lst)?; @@ -493,12 +529,12 @@ where Ok(self) } - fn bind2(&self, addr: A) -> io::Result> { + fn bind2(&self, addrs: A) -> io::Result> { let mut err = None; let mut success = false; let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { + for addr in addrs.to_socket_addrs()? { match create_tcp_listener(addr, self.backlog) { Ok(lst) => { success = true; @@ -520,42 +556,50 @@ where } } - #[cfg(feature = "openssl")] - /// Start listening for incoming tls connections. + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections + /// using OpenSSL. /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_openssl
(mut self, addr: A, builder: SslAcceptorBuilder) -> io::Result + /// See [bind()](Self::bind) for more details on `addrs` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "openssl")] + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] + pub fn bind_openssl(mut self, addrs: A, builder: SslAcceptorBuilder) -> io::Result where A: net::ToSocketAddrs, { - let sockets = self.bind2(addr)?; + let sockets = self.bind2(addrs)?; let acceptor = openssl_acceptor(builder)?; for lst in sockets { - self = self.listen_ssl_inner(lst, acceptor.clone())?; + self = self.listen_openssl_inner(lst, acceptor.clone())?; } Ok(self) } - #[cfg(feature = "rustls")] - /// Start listening for incoming tls connections. + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections + /// using Rustls. /// - /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones + /// See [bind()](Self::bind) for more details on `addrs` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn bind_rustls( mut self, - addr: A, + addrs: A, config: RustlsServerConfig, ) -> io::Result { - let sockets = self.bind2(addr)?; + let sockets = self.bind2(addrs)?; for lst in sockets { self = self.listen_rustls_inner(lst, config.clone())?; } Ok(self) } - #[cfg(unix)] /// Start listening for unix domain (UDS) connections on existing listener. + #[cfg(unix)] pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_http::Protocol; use actix_rt::net::UnixStream; @@ -665,25 +709,16 @@ where { /// Start listening for incoming connections. /// - /// This method starts number of HTTP workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. + /// # Workers + /// This method starts a number of HTTP workers in separate threads. The number of workers in a + /// set is defined by [`workers()`](Self::workers) or, by default, the number of the machine's + /// physical cores. One worker set is created for each socket address to be bound. For example, + /// if workers is set to 4, and there are 2 addresses to bind, then 8 worker threads will be + /// spawned. /// - /// This methods panics if no socket address can be bound or an `Actix` system is not yet - /// configured. - /// - /// ```no_run - /// use std::io; - /// use actix_web::{web, App, HttpResponse, HttpServer}; - /// - /// #[actix_rt::main] - /// async fn main() -> io::Result<()> { - /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0")? - /// .run() - /// .await - /// } - /// ``` + /// # Panics + /// This methods panics if no socket addresses were successfully bound or if no Tokio runtime + /// is set up. pub fn run(self) -> Server { self.builder.run() } From 19aa14a9d6c780395a9f592955f20822c21d9339 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 31 Jul 2022 22:10:51 +0100 Subject: [PATCH 494/861] re-order HttpServer methods for better docs --- actix-web/src/server.rs | 562 ++++++++++++++++++++-------------------- 1 file changed, 281 insertions(+), 281 deletions(-) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 2297dfa98..5021e0a2d 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -113,31 +113,6 @@ where } } - /// Sets function that will be called once before each connection is handled. - /// It will receive a `&std::any::Any`, which contains underlying connection type and an - /// [Extensions] container so that connection data can be accessed in middleware and handlers. - /// - /// # Connection Types - /// - `actix_tls::accept::openssl::TlsStream` when using openssl. - /// - `actix_tls::accept::rustls::TlsStream` when using rustls. - /// - `actix_web::rt::net::TcpStream` when no encryption is used. - /// - /// See the `on_connect` example for additional details. - pub fn on_connect(self, f: CB) -> HttpServer - where - CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, - { - HttpServer { - factory: self.factory, - config: self.config, - backlog: self.backlog, - sockets: self.sockets, - builder: self.builder, - on_connect_fn: Some(Arc::new(f)), - _phantom: PhantomData, - } - } - /// Sets number of workers to start (per bind address). /// /// By default, the number of available physical CPUs is used as the worker count. @@ -146,16 +121,23 @@ where self } + /// Sets server keep-alive preference. + /// + /// By default keep-alive is set to 5 seconds. + pub fn keep_alive>(self, val: T) -> Self { + self.config.lock().unwrap().keep_alive = val.into(); + self + } + /// Sets the maximum number of pending connections. /// - /// This refers to the number of clients that can be waiting to be served. - /// Exceeding this number results in the client getting an error when - /// attempting to connect. It should only affect servers under significant - /// load. + /// This refers to the number of clients that can be waiting to be served. Exceeding this number + /// results in the client getting an error when attempting to connect. It should only affect + /// servers under significant load. /// - /// Generally set in the 64-2048 range. Default value is 2048. + /// Generally set in the 64–2048 range. Default value is 2048. /// - /// This method should be called before `bind()` method call. + /// This method will have no effect if called after a `bind()`. pub fn backlog(mut self, backlog: u32) -> Self { self.backlog = backlog; self.builder = self.builder.backlog(backlog); @@ -196,14 +178,6 @@ where self } - /// Sets server keep-alive preference. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(self, val: T) -> Self { - self.config.lock().unwrap().keep_alive = val.into(); - self - } - /// Sets server client timeout for first request. /// /// Defines a timeout for reading client request head. If a client does not transmit the entire @@ -260,6 +234,32 @@ where self.client_disconnect_timeout(Duration::from_millis(dur)) } + /// Sets function that will be called once before each connection is handled. + /// + /// It will receive a `&std::any::Any`, which contains underlying connection type and an + /// [Extensions] container so that connection data can be accessed in middleware and handlers. + /// + /// # Connection Types + /// - `actix_tls::accept::openssl::TlsStream` when using OpenSSL. + /// - `actix_tls::accept::rustls::TlsStream` when using Rustls. + /// - `actix_web::rt::net::TcpStream` when no encryption is used. + /// + /// See the `on_connect` example for additional details. + pub fn on_connect(self, f: CB) -> HttpServer + where + CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, + { + HttpServer { + factory: self.factory, + config: self.config, + backlog: self.backlog, + sockets: self.sockets, + builder: self.builder, + on_connect_fn: Some(Arc::new(f)), + _phantom: PhantomData, + } + } + /// Sets server host name. /// /// Host name is used by application router as a hostname for url generation. Check @@ -271,7 +271,7 @@ where self } - /// Stops `actix_rt` `System` after server shutdown. + /// Flags the `System` to exit after server shutdown. /// /// Does nothing when running under `#[tokio::main]` runtime. pub fn system_exit(mut self) -> Self { @@ -310,187 +310,6 @@ where self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() } - /// Binds to existing listener for accepting incoming connection requests. - /// - /// No changes are made to `lst`'s configuration. Ensure it is configured properly before - /// passing ownership to `listen()`. - pub fn listen(mut self, lst: net::TcpListener) -> io::Result { - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let addr = lst.local_addr().unwrap(); - - self.sockets.push(Socket { - addr, - scheme: "http", - }); - - let on_connect_fn = self.on_connect_fn.clone(); - - self.builder = - self.builder - .listen(format!("actix-web-service-{}", addr), lst, move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - - let mut svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) - .local_addr(addr); - - if let Some(handler) = on_connect_fn.clone() { - svc = svc.on_connect_ext(move |io: &_, ext: _| { - (handler)(io as &dyn Any, ext) - }) - }; - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - svc.finish(map_config(fac, move |_| { - AppConfig::new(false, host.clone(), addr) - })) - .tcp() - })?; - Ok(self) - } - - /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. - /// - /// See [listen()](Self::listen) for more details on the `lst` argument. - /// - /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. - #[cfg(feature = "openssl")] - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] - pub fn listen_openssl( - self, - lst: net::TcpListener, - builder: SslAcceptorBuilder, - ) -> io::Result { - self.listen_openssl_inner(lst, openssl_acceptor(builder)?) - } - - #[cfg(feature = "openssl")] - fn listen_openssl_inner( - mut self, - lst: net::TcpListener, - acceptor: SslAcceptor, - ) -> io::Result { - let factory = self.factory.clone(); - let cfg = self.config.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "https", - }); - - let on_connect_fn = self.on_connect_fn.clone(); - - self.builder = - self.builder - .listen(format!("actix-web-service-{}", addr), lst, move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) - .local_addr(addr); - - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) - } else { - svc - }; - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - // false positive lint (?) - #[allow(clippy::significant_drop_in_scrutinee)] - let acceptor_config = match c.tls_handshake_timeout { - Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), - None => TlsAcceptorConfig::default(), - }; - - svc.finish(map_config(fac, move |_| { - AppConfig::new(true, host.clone(), addr) - })) - .openssl_with_config(acceptor.clone(), acceptor_config) - })?; - - Ok(self) - } - - /// Binds to existing listening for accepting incoming TLS connection requests using Rustls. - /// - /// See [listen()](Self::listen) for more details on the `lst` argument. - /// - /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. - #[cfg(feature = "rustls")] - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] - pub fn listen_rustls( - self, - lst: net::TcpListener, - config: RustlsServerConfig, - ) -> io::Result { - self.listen_rustls_inner(lst, config) - } - - #[cfg(feature = "rustls")] - fn listen_rustls_inner( - mut self, - lst: net::TcpListener, - config: RustlsServerConfig, - ) -> io::Result { - let factory = self.factory.clone(); - let cfg = self.config.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "https", - }); - - let on_connect_fn = self.on_connect_fn.clone(); - - self.builder = - self.builder - .listen(format!("actix-web-service-{}", addr), lst, move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout); - - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) - } else { - svc - }; - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - let acceptor_config = match c.tls_handshake_timeout { - Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), - None => TlsAcceptorConfig::default(), - }; - - svc.finish(map_config(fac, move |_| { - AppConfig::new(true, host.clone(), addr) - })) - .rustls_with_config(config.clone(), acceptor_config) - })?; - - Ok(self) - } - /// Resolves socket address(es) and binds server to created listener(s). /// /// # Hostname Resolution @@ -556,10 +375,30 @@ where } } + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections + /// using Rustls. + /// + /// See [`bind()`](Self::bind) for more details on `addrs` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] + pub fn bind_rustls( + mut self, + addrs: A, + config: RustlsServerConfig, + ) -> io::Result { + let sockets = self.bind2(addrs)?; + for lst in sockets { + self = self.listen_rustls_inner(lst, config.clone())?; + } + Ok(self) + } + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections /// using OpenSSL. /// - /// See [bind()](Self::bind) for more details on `addrs` argument. + /// See [`bind()`](Self::bind) for more details on `addrs` argument. /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "openssl")] @@ -578,27 +417,236 @@ where Ok(self) } - /// Resolves socket address(es) and binds server to created listener(s) for TLS connections - /// using Rustls. + /// Binds to existing listener for accepting incoming connection requests. /// - /// See [bind()](Self::bind) for more details on `addrs` argument. + /// No changes are made to `lst`'s configuration. Ensure it is configured properly before + /// passing ownership to `listen()`. + pub fn listen(mut self, lst: net::TcpListener) -> io::Result { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + let addr = lst.local_addr().unwrap(); + + self.sockets.push(Socket { + addr, + scheme: "http", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let mut svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) + .local_addr(addr); + + if let Some(handler) = on_connect_fn.clone() { + svc = svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| { + AppConfig::new(false, host.clone(), addr) + })) + .tcp() + })?; + Ok(self) + } + + /// Binds to existing listener for accepting incoming TLS connection requests using Rustls. + /// + /// See [`listen()`](Self::listen) for more details on the `lst` argument. /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] - pub fn bind_rustls( - mut self, - addrs: A, + pub fn listen_rustls( + self, + lst: net::TcpListener, config: RustlsServerConfig, ) -> io::Result { - let sockets = self.bind2(addrs)?; - for lst in sockets { - self = self.listen_rustls_inner(lst, config.clone())?; - } + self.listen_rustls_inner(lst, config) + } + + #[cfg(feature = "rustls")] + fn listen_rustls_inner( + mut self, + lst: net::TcpListener, + config: RustlsServerConfig, + ) -> io::Result { + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "https", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + + svc.finish(map_config(fac, move |_| { + AppConfig::new(true, host.clone(), addr) + })) + .rustls_with_config(config.clone(), acceptor_config) + })?; + Ok(self) } - /// Start listening for unix domain (UDS) connections on existing listener. + /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. + /// + /// See [`listen()`](Self::listen) for more details on the `lst` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "openssl")] + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] + pub fn listen_openssl( + self, + lst: net::TcpListener, + builder: SslAcceptorBuilder, + ) -> io::Result { + self.listen_openssl_inner(lst, openssl_acceptor(builder)?) + } + + #[cfg(feature = "openssl")] + fn listen_openssl_inner( + mut self, + lst: net::TcpListener, + acceptor: SslAcceptor, + ) -> io::Result { + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "https", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) + .local_addr(addr); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + // false positive lint (?) + #[allow(clippy::significant_drop_in_scrutinee)] + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + + svc.finish(map_config(fac, move |_| { + AppConfig::new(true, host.clone(), addr) + })) + .openssl_with_config(acceptor.clone(), acceptor_config) + })?; + + Ok(self) + } + + /// Opens Unix Domain Socket (UDS) from `uds` path and binds server to created listener. + #[cfg(unix)] + pub fn bind_uds(mut self, uds_path: A) -> io::Result + where + A: AsRef, + { + use actix_http::Protocol; + use actix_rt::net::UnixStream; + use actix_service::{fn_service, ServiceFactoryExt as _}; + + let cfg = self.config.clone(); + let factory = self.factory.clone(); + let socket_addr = + net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); + + self.sockets.push(Socket { + scheme: "http", + addr: socket_addr, + }); + + self.builder = self.builder.bind_uds( + format!("actix-web-service-{:?}", uds_path.as_ref()), + uds_path, + move || { + let c = cfg.lock().unwrap(); + let config = AppConfig::new( + false, + c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, + ); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) + .finish(map_config(fac, move |_| config.clone())), + ) + }, + )?; + + Ok(self) + } + + /// Binds to existing Unix Domain Socket (UDS) listener. #[cfg(unix)] pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_http::Protocol; @@ -646,54 +694,6 @@ where })?; Ok(self) } - - /// Start listening for incoming unix domain connections. - #[cfg(unix)] - pub fn bind_uds(mut self, addr: A) -> io::Result - where - A: AsRef, - { - use actix_http::Protocol; - use actix_rt::net::UnixStream; - use actix_service::{fn_service, ServiceFactoryExt as _}; - - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let socket_addr = - net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); - - self.sockets.push(Socket { - scheme: "http", - addr: socket_addr, - }); - - self.builder = self.builder.bind_uds( - format!("actix-web-service-{:?}", addr.as_ref()), - addr, - move || { - let c = cfg.lock().unwrap(); - let config = AppConfig::new( - false, - c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), - socket_addr, - ); - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) - .finish(map_config(fac, move |_| config.clone())), - ) - }, - )?; - - Ok(self) - } } impl HttpServer From ea764b1d57a7e6db6a093513af596872a17c2699 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 31 Jul 2022 23:40:09 +0100 Subject: [PATCH 495/861] add feature annotations to docs --- actix-http/src/builder.rs | 1 + actix-http/src/error.rs | 2 ++ actix-http/src/h1/service.rs | 2 ++ actix-http/src/h2/service.rs | 2 ++ actix-http/src/lib.rs | 3 +++ actix-http/src/service.rs | 4 ++++ actix-web/src/request.rs | 2 ++ 7 files changed, 16 insertions(+) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 526a23d53..71b933835 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -211,6 +211,7 @@ where /// Finish service configuration and create a HTTP service for HTTP/2 protocol. #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn h2(self, service: F) -> crate::h2::H2Service where F: IntoServiceFactory, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 41522a254..2d443369d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -294,6 +294,7 @@ impl std::error::Error for PayloadError { PayloadError::Overflow => None, PayloadError::UnknownLength => None, #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] PayloadError::Http2Payload(err) => Some(err), PayloadError::Io(err) => Some(err), } @@ -351,6 +352,7 @@ pub enum DispatchError { /// HTTP/2 error. #[display(fmt = "{}", _0)] #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] H2(h2::Error), /// The first request did not complete within the specified timeout. diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index a791ea8c3..e4d90424d 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -134,6 +134,7 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -196,6 +197,7 @@ mod rustls { U::InitError: fmt::Debug, { /// Create Rustls based service. + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, config: ServerConfig, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index e526918c7..2a45fc1dc 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -117,6 +117,7 @@ mod openssl { B: MessageBody + 'static, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -164,6 +165,7 @@ mod rustls { B: MessageBody + 'static, { /// Create Rustls based service. + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, mut config: ServerConfig, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 184049860..864db4986 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -40,6 +40,7 @@ pub mod error; mod extensions; pub mod h1; #[cfg(feature = "http2")] +#[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub mod h2; pub mod header; mod helpers; @@ -54,6 +55,7 @@ mod responses; mod service; pub mod test; #[cfg(feature = "ws")] +#[cfg_attr(docsrs, doc(cfg(feature = "ws")))] pub mod ws; pub use self::builder::HttpServiceBuilder; @@ -71,6 +73,7 @@ pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; #[cfg(any(feature = "openssl", feature = "rustls"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] pub use self::service::TlsAcceptorConfig; /// A major HTTP protocol version. diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 27029cb8e..bcca5b188 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -239,6 +239,7 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -253,6 +254,7 @@ mod openssl { } /// Create OpenSSL based service with custom TLS acceptor configuration. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl_with_config( self, acceptor: SslAcceptor, @@ -332,6 +334,7 @@ mod rustls { U::InitError: fmt::Debug, { /// Create Rustls based service. + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, config: ServerConfig, @@ -346,6 +349,7 @@ mod rustls { } /// Create Rustls based service with custom TLS acceptor configuration. + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls_with_config( self, mut config: ServerConfig, diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index 2998c7010..e2a9bd4e5 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -311,6 +311,7 @@ impl HttpRequest { /// Load request cookies. #[cfg(feature = "cookies")] + #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookies(&self) -> Result>>, CookieParseError> { use actix_http::header::COOKIE; @@ -334,6 +335,7 @@ impl HttpRequest { /// Return request cookie. #[cfg(feature = "cookies")] + #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { From c9f91796df920daf56ad87f31279b1260cf6088f Mon Sep 17 00:00:00 2001 From: liushuyu Date: Wed, 24 Aug 2022 20:12:58 -0600 Subject: [PATCH 496/861] awc: correctly handle redirections that begins with `//` (#2840) --- awc/CHANGES.md | 5 ++++ awc/src/middleware/redirect.rs | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e229a6d96..9cf294616 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,11 @@ ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +### Fixed +- Fixed handling of redirection requests that begin with `//`. [#2840] + +[#2840]: https://github.com/actix/actix-web/pull/2840 + ## 3.0.0 - 2022-03-07 ### Dependencies diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index d48822168..67ef5d76f 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -257,6 +257,16 @@ fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result = scheme.as_bytes().to_vec(); + full_url.push(b':'); + full_url.extend(location.as_bytes()); + + return Uri::try_from(full_url) + .map_err(|_| SendRequestError::Url(InvalidUrl::MissingScheme)); + } // when scheme or authority is missing treat the location value as path and query // recover error where location does not have leading slash let path = if location.as_bytes().starts_with(b"/") { @@ -588,6 +598,41 @@ mod tests { assert_eq!(res.status().as_u16(), 200); } + #[actix_rt::test] + async fn test_double_slash_redirect() { + let client = ClientBuilder::new() + .disable_redirects() + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = actix_test::start(|| { + App::new() + .service(web::resource("/test").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::BadRequest()) + }))) + .service( + web::resource("/").route(web::to(|req: HttpRequest| async move { + Ok::<_, Error>( + HttpResponse::Found() + .append_header(( + "location", + format!( + "//localhost:{}/test", + req.app_config().local_addr().port() + ) + .as_str(), + )) + .finish(), + ) + })), + ) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status().as_u16(), 400); + } + #[actix_rt::test] async fn test_remove_sensitive_headers() { fn gen_headers() -> header::HeaderMap { From f220719fae45dc3b7ee998e09d87fa908f9cb525 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 25 Aug 2022 03:13:31 +0100 Subject: [PATCH 497/861] prepare awc release 3.0.1 --- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 2 +- awc/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9cf294616..3a5a49c84 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.0.1 - 2022-08-25 ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0250091bf..2f0027725 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0" +version = "3.0.1" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/awc/README.md b/awc/README.md index db70f7332..9f47e663b 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0)](https://docs.rs/awc/3.0.0) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.1)](https://docs.rs/awc/3.0.1) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0/status.svg)](https://deps.rs/crate/awc/3.0.0) +[![Dependency Status](https://deps.rs/crate/awc/3.0.1/status.svg)](https://deps.rs/crate/awc/3.0.1) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources From 056de320f0bdf9878be2dd418dc1564d4b8a194f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 25 Aug 2022 03:17:48 +0100 Subject: [PATCH 498/861] fix scope doc example fixes #2843 --- actix-web/src/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index f8c042a5f..07eb1093a 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -40,7 +40,7 @@ type Guards = Vec>; /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( -/// web::scope("/{project_id}/") +/// web::scope("/{project_id}") /// .service(web::resource("/path1").to(|| async { "OK" })) /// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) /// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed))) From 679f61cf3751ce9ca53ab64b01baef33b83db937 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 27 Aug 2022 13:14:16 +0100 Subject: [PATCH 499/861] bump msrv to 1.59 --- .github/workflows/ci.yml | 2 +- actix-files/CHANGES.md | 1 + actix-files/README.md | 2 +- actix-http-test/CHANGES.md | 1 + actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/README.md | 2 +- actix-multipart/CHANGES.md | 2 +- actix-multipart/README.md | 2 +- actix-router/CHANGES.md | 2 +- actix-test/CHANGES.md | 1 + actix-web-actors/README.md | 2 +- actix-web-codegen/CHANGES.md | 2 +- actix-web-codegen/README.md | 2 +- actix-web-codegen/tests/trybuild.rs | 2 +- .../route-duplicate-method-fail.stderr | 20 ++++++++++++------- .../trybuild/route-missing-method-fail.stderr | 20 ++++++++++++------- .../route-unexpected-method-fail.stderr | 20 ++++++++++++------- .../trybuild/routes-missing-args-fail.stderr | 20 ++++++++++++------- .../routes-missing-method-fail.stderr | 20 ++++++++++++------- actix-web/CHANGES.md | 2 +- actix-web/README.md | 4 ++-- awc/CHANGES.md | 2 ++ clippy.toml | 2 +- 24 files changed, 88 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ea920808..de1e1fe18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.57.0 # MSRV + - 1.59.0 # MSRV - stable name: ${{ matrix.target.name }} / ${{ matrix.version }} diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 5c0a48024..a71bf14fa 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. ## 0.6.2 - 2022-07-23 diff --git a/actix-files/README.md b/actix-files/README.md index c3204a68c..a5078c8d5 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.2)](https://docs.rs/actix-files/0.6.2) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.2/status.svg)](https://deps.rs/crate/actix-files/0.6.2) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 9aad2e4ba..028fe3ddc 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.59. ## 3.0.0 - 2022-07-24 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index ec2bd769c..25e7c684e 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0)](https://docs.rs/actix-http-test/3.0.0) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 785a1b13f..f13f0e56e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. + ### Fixed - Avoid possibility of dispatcher getting stuck while back-pressuring I/O. [#2369] diff --git a/actix-http/README.md b/actix-http/README.md index 787d2f653..ab8f069d9 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.1)](https://docs.rs/actix-http/3.2.1) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.2.1/status.svg)](https://deps.rs/crate/actix-http/3.2.1) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index ed5c97e1d..d0da40fb4 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 0b1e2df17..21999716c 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 1e4fc41f2..ec4676ea3 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. ## 0.5.0 - 2022-02-22 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index bf5d9324f..c8fe54203 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. ## 0.1.0 - 2022-07-24 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 8d64c0851..a0578994c 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 6b525a441..6f750703f 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -2,7 +2,7 @@ ## Unreleased - 2022-xx-xx - Add `#[routes]` macro to support multiple paths for one handler. [#2718] -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. [#2718]: https://github.com/actix/actix-web/pull/2718 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 26f070f18..3f129dba5 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) -![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 1f7996fd0..26aec7d28 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.57)] // MSRV +#[rustversion::stable(1.59)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index fe9274bc8..7eac84f3e 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -4,10 +4,16 @@ error: HTTP method defined more than once: `GET` 3 | #[route("/", method="GET", method="GET")] | ^^^^^ -error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> tests/trybuild/route-duplicate-method-fail.rs:12:55 - | -12 | let srv = actix_test::start(|| App::new().service(index)); - | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` - | | - | required by a bound introduced by this call +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/route-duplicate-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call + | +note: required by a bound in `App::::service` + --> $WORKSPACE/actix-web/src/app.rs + | + | F: HttpServiceFactory + 'static, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index 284b2cf4a..bc8497c10 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -6,10 +6,16 @@ error: The #[route(..)] macro requires at least one `method` attribute | = note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> tests/trybuild/route-missing-method-fail.rs:12:55 - | -12 | let srv = actix_test::start(|| App::new().service(index)); - | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` - | | - | required by a bound introduced by this call +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/route-missing-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call + | +note: required by a bound in `App::::service` + --> $WORKSPACE/actix-web/src/app.rs + | + | F: HttpServiceFactory + 'static, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index 804ba69f3..3df5d9f5a 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -4,10 +4,16 @@ error: Unexpected HTTP method: `UNEXPECTED` 3 | #[route("/", method="UNEXPECTED")] | ^^^^^^^^^^^^ -error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> tests/trybuild/route-unexpected-method-fail.rs:12:55 - | -12 | let srv = actix_test::start(|| App::new().service(index)); - | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` - | | - | required by a bound introduced by this call +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/route-unexpected-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call + | +note: required by a bound in `App::::service` + --> $WORKSPACE/actix-web/src/app.rs + | + | F: HttpServiceFactory + 'static, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` diff --git a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr index 8efe0682b..785d6f326 100644 --- a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr +++ b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr @@ -12,10 +12,16 @@ error: Invalid input for macro 4 | #[get] | ^^^^^^ -error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> tests/trybuild/routes-missing-args-fail.rs:13:55 - | -13 | let srv = actix_test::start(|| App::new().service(index)); - | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` - | | - | required by a bound introduced by this call +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-args-fail.rs:13:55 + | +13 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call + | +note: required by a bound in `App::::service` + --> $WORKSPACE/actix-web/src/app.rs + | + | F: HttpServiceFactory + 'static, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` diff --git a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr index b3795d74a..38a6d2f9b 100644 --- a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr @@ -6,10 +6,16 @@ error: The #[routes] macro requires at least one `#[(..)]` attribute. | = note: this error originates in the attribute macro `routes` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> tests/trybuild/routes-missing-method-fail.rs:12:55 - | -12 | let srv = actix_test::start(|| App::new().service(index)); - | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` - | | - | required by a bound introduced by this call +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call + | +note: required by a bound in `App::::service` + --> $WORKSPACE/actix-web/src/app.rs + | + | F: HttpServiceFactory + 'static, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index f38282b41..b35007c6b 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -7,7 +7,7 @@ - Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752] ### Changed -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. [#2718]: https://github.com/actix/actix-web/pull/2718 [#2752]: https://github.com/actix/actix-web/pull/2752 diff --git a/actix-web/README.md b/actix-web/README.md index fdd4a8648..9f00dd917 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) -![MSRV](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) +![MSRV](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
@@ -33,7 +33,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.57+ +- Runs on stable Rust 1.59+ ## Documentation diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3a5a49c84..7892d9339 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2022-xx-xx +### Changed +- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. ## 3.0.1 - 2022-08-25 diff --git a/clippy.toml b/clippy.toml index 5cccb362c..abe19b3a0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.57" +msrv = "1.59" From c993055fc820542863939b63bb3a3c5328febc5c Mon Sep 17 00:00:00 2001 From: Juan Aguilar Date: Tue, 30 Aug 2022 10:34:46 +0200 Subject: [PATCH 500/861] replace askama_escape in favor of v_htmlescape (#2824) --- actix-files/Cargo.toml | 2 +- actix-files/src/directory.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 30356d81a..33de0e6d9 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -27,7 +27,6 @@ actix-service = "2" actix-utils = "3" actix-web = { version = "4", default-features = false } -askama_escape = "0.10" bitflags = "1" bytes = "1" derive_more = "0.99.5" @@ -38,6 +37,7 @@ mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" +v_htmlescape= "0.15" # experimental-io-uring [target.'cfg(target_os = "linux")'.dependencies] diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 32dd6365b..3af53a31a 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -1,8 +1,8 @@ use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf}; use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse}; -use askama_escape::{escape as escape_html_entity, Html}; use percent_encoding::{utf8_percent_encode, CONTROLS}; +use v_htmlescape::escape as escape_html_entity; /// A directory; responds with the generated directory listing. #[derive(Debug)] @@ -59,7 +59,7 @@ macro_rules! encode_file_url { /// ``` macro_rules! encode_file_name { ($entry:ident) => { - escape_html_entity(&$entry.file_name().to_string_lossy(), Html) + escape_html_entity(&$entry.file_name().to_string_lossy()) }; } From 0b5b4dcbf37e26bfe98be71bbc1ff839c289da8f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 3 Sep 2022 21:56:37 +0100 Subject: [PATCH 501/861] reduce size of docs branch --- .github/workflows/upload-doc.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 94a2ddfbe..d35f2285e 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -30,6 +30,6 @@ jobs: - name: Deploy to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: target/doc + github_token: ${{ secrets.GITHUB_TOKEN }} + folder: target/doc + single-commit: true From 35b0fd1a85ad5a96377ad6291940646ed644ffde Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 3 Sep 2022 22:05:28 +0100 Subject: [PATCH 502/861] specify branch in doc job --- .github/workflows/upload-doc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index d35f2285e..2d0ffe198 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -31,5 +31,6 @@ jobs: uses: JamesIves/github-pages-deploy-action@3.7.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages folder: target/doc single-commit: true From 99bf774e94021789fb10d6f6cb77ef4793128730 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 3 Sep 2022 22:15:59 +0100 Subject: [PATCH 503/861] update gh-pages deploy action --- .github/workflows/upload-doc.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 2d0ffe198..07f839e34 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -28,9 +28,7 @@ jobs: run: echo '' > target/doc/index.html - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@3.7.1 + uses: JamesIves/github-pages-deploy-action@v4.4.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: gh-pages folder: target/doc single-commit: true From 386258c285822e35f93ffb41fb2c94c4c0b249c0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 6 Sep 2022 10:13:10 +0100 Subject: [PATCH 504/861] clarify worker_max_blocking_threads default --- actix-web/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 5021e0a2d..3a8897f11 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -172,7 +172,7 @@ where /// /// One thread pool is set up **per worker**; not shared across workers. /// - /// By default set to 512 / workers. + /// By default set to 512 divided by the number of workers. pub fn worker_max_blocking_threads(mut self, num: usize) -> Self { self.builder = self.builder.worker_max_blocking_threads(num); self From 037740bf62485a840a74d3b30710cfd176665830 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 11 Sep 2022 16:41:29 +0100 Subject: [PATCH 505/861] prepare actix-http release 3.2.2 --- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- scripts/unreleased | 6 ++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f13f0e56e..c4386bb4d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.2.2 - 2022-09-11 ### Changed - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ba8c80e64..30e436160 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.2.1" +version = "3.2.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index ab8f069d9..994cf97c6 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.1)](https://docs.rs/actix-http/3.2.1) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.2)](https://docs.rs/actix-http/3.2.2) ![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.2.1/status.svg)](https://deps.rs/crate/actix-http/3.2.1) +[![dependency status](https://deps.rs/crate/actix-http/3.2.2/status.svg)](https://deps.rs/crate/actix-http/3.2.2) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 507e92752..32ea49a24 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.2" -actix-http = "3.0.0" +actix-http = "3" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/scripts/unreleased b/scripts/unreleased index e664c0879..6738ad090 100755 --- a/scripts/unreleased +++ b/scripts/unreleased @@ -45,6 +45,8 @@ unreleased_for() { cat "$CHANGE_CHUNK_FILE" } -for f in $(fd --absolute-path 'CHANGE\w+.md'); do - unreleased_for $(dirname $f) +files=$(fd --threads=1 --min-depth=2 --absolute-path 'CHANGE\w+.md') + +for f in $files; do + unreleased_for $(dirname $f) || true done From b59a96d9d7e7b901fa544112a1d410fda1560c9e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 11 Sep 2022 16:42:28 +0100 Subject: [PATCH 506/861] prepare actix-web-codegen release 4.1.0 --- actix-web-codegen/CHANGES.md | 3 +++ actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/README.md | 4 ++-- actix-web/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 6f750703f..cb37bfdb0 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 4.1.0 - 2022-09-11 - Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 0c3b70589..eb1343149 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "4.0.1" +version = "4.1.0" description = "Routing and runtime macros for Actix Web" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 3f129dba5..821236e47 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,11 +3,11 @@ > Routing and runtime macros for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.1.0)](https://docs.rs/actix-web-codegen/4.1.0) ![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
-[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) +[![dependency status](https://deps.rs/crate/actix-web-codegen/4.1.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.1.0) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 12806e686..7918aa310 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -73,7 +73,7 @@ actix-tls = { version = "3", default-features = false, optional = true } actix-http = { version = "3", features = ["http2", "ws"] } actix-router = "0.5" -actix-web-codegen = { version = "4", optional = true } +actix-web-codegen = { version = "4.1", optional = true } ahash = "0.7" bytes = "1" From 7767cf30710ec2a8666b0c9e6f578aa590038a54 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 11 Sep 2022 16:44:46 +0100 Subject: [PATCH 507/861] prepare actix-web release 4.2.0 --- actix-web-codegen/Cargo.toml | 4 ++-- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index eb1343149..a89dde2ee 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -24,8 +24,8 @@ syn = { version = "1", features = ["full", "extra-traits"] } actix-macros = "0.2.3" actix-rt = "2.2" actix-test = "0.1" -actix-utils = "3.0.0" -actix-web = "4.0.0" +actix-utils = "3" +actix-web = "4" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } trybuild = "1" diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index b35007c6b..03ea1eba8 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2022-xx-xx + + +## 4.2.0 - 2022-09-11 ### Added - Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Add `ServiceRequest::{parts, request}()` getter methods. [#2786] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7918aa310..27a77bfc0 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.1.0" +version = "4.2.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index 9f00dd917..17bd5a7ce 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.2.0)](https://docs.rs/actix-web/4.2.0) ![MSRV](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0) +[![Dependency Status](https://deps.rs/crate/actix-web/4.2.0/status.svg)](https://deps.rs/crate/actix-web/4.2.0)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From a9e44bcf070b9f51b34f29696b5afd0ed351e7bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Sep 2022 10:42:22 +0100 Subject: [PATCH 508/861] fix -http version to 3.2.2 (#2871) fixes #2869 --- actix-web/CHANGES.md | 4 ++++ actix-web/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 03ea1eba8..ad865fb2e 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,10 @@ # Changelog ## Unreleased - 2022-xx-xx +### Fixed +- Bump minimum version of `actix-http` dependency to fix compatibility issue. [#2871] + +[#2871]: https://github.com/actix/actix-web/pull/2871 ## 4.2.0 - 2022-09-11 diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 27a77bfc0..a06b06331 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,7 +71,7 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3", features = ["http2", "ws"] } +actix-http = { version = "3.2.2", features = ["http2", "ws"] } actix-router = "0.5" actix-web-codegen = { version = "4.1", optional = true } From 40f7ab38d29c313d1532c4588012662ed724110c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 12 Sep 2022 10:43:03 +0100 Subject: [PATCH 509/861] prepare actix-web release 4.2.1 --- actix-web/CHANGES.md | 3 +++ actix-web/Cargo.toml | 2 +- actix-web/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index ad865fb2e..d11108c2a 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased - 2022-xx-xx + + +## 4.2.1 - 2022-09-12 ### Fixed - Bump minimum version of `actix-http` dependency to fix compatibility issue. [#2871] diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index a06b06331..d57e52f36 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.2.0" +version = "4.2.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-web/README.md b/actix-web/README.md index 17bd5a7ce..65076e0b8 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.2.0)](https://docs.rs/actix-web/4.2.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.2.1)](https://docs.rs/actix-web/4.2.1) ![MSRV](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.2.0/status.svg)](https://deps.rs/crate/actix-web/4.2.0) +[![Dependency Status](https://deps.rs/crate/actix-web/4.2.1/status.svg)](https://deps.rs/crate/actix-web/4.2.1)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From 909461087c608855aa252bf2b3343aac53210c75 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 13 Sep 2022 01:19:25 +0100 Subject: [PATCH 510/861] add `ContentDisposition::attachment` constructor (#2867) --- actix-web/CHANGES.md | 4 ++++ .../src/http/header/content_disposition.rs | 24 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index d11108c2a..9ded67e35 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,10 @@ # Changelog ## Unreleased - 2022-xx-xx +### Added +- Add `ContentDisposition::attachment` constructor. [#2867] + +[#2867]: https://github.com/actix/actix-web/pull/2867 ## 4.2.1 - 2022-09-12 diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs index 0bb459193..f743302a2 100644 --- a/actix-web/src/http/header/content_disposition.rs +++ b/actix-web/src/http/header/content_disposition.rs @@ -79,7 +79,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert!(param.is_filename()); /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from @@ -302,7 +302,7 @@ impl DispositionParam { /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. /// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ContentDisposition { /// The disposition type pub disposition: DispositionType, @@ -312,16 +312,36 @@ pub struct ContentDisposition { } impl ContentDisposition { + /// Constructs a Content-Disposition header suitable for downloads. + /// + /// # Examples + /// ``` + /// use actix_web::http::header::{ContentDisposition, TryIntoHeaderValue as _}; + /// + /// let cd = ContentDisposition::attachment("files.zip"); + /// + /// let cd_val = cd.try_into_value().unwrap(); + /// assert_eq!(cd_val, "attachment; filename=\"files.zip\""); + /// ``` + pub fn attachment(filename: impl Into) -> Self { + Self { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename(filename.into())], + } + } + /// Parse a raw Content-Disposition header value. pub fn from_raw(hv: &header::HeaderValue) -> Result { // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible // ASCII characters. So `hv.as_bytes` is necessary here. let hv = String::from_utf8(hv.as_bytes().to_vec()) .map_err(|_| crate::error::ParseError::Header)?; + let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); if disp_type.is_empty() { return Err(crate::error::ParseError::Header); } + let mut cd = ContentDisposition { disposition: disp_type.into(), parameters: Vec::new(), From c73fba16ce81209dfac70def01b5f98feb982f4f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Sep 2022 11:23:22 +0100 Subject: [PATCH 511/861] implement MessageBody for mut B (#2868) --- actix-http/CHANGES.md | 5 +++ actix-http/src/body/message_body.rs | 66 ++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c4386bb4d..816d4b71f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2022-xx-xx +### Added +- Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868] +- Implement `MessageBody` for `Pin` where `B::Target: MessageBody`. [#2868] + +[#2868]: https://github.com/actix/actix-web/pull/2868 ## 3.2.2 - 2022-09-11 diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index ab742b9cd..bac2fece1 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -120,8 +120,28 @@ pub trait MessageBody { } mod foreign_impls { + use std::ops::DerefMut; + use super::*; + impl MessageBody for &mut B + where + B: MessageBody + Unpin + ?Sized, + { + type Error = B::Error; + + fn size(&self) -> BodySize { + (&**self).size() + } + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + Pin::new(&mut **self).poll_next(cx) + } + } + impl MessageBody for Infallible { type Error = Infallible; @@ -179,8 +199,9 @@ mod foreign_impls { } } - impl MessageBody for Pin> + impl MessageBody for Pin where + T: DerefMut + Unpin, B: MessageBody + ?Sized, { type Error = B::Error; @@ -445,6 +466,7 @@ mod tests { use actix_rt::pin; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; + use futures_util::stream; use super::*; use crate::body::{self, EitherBody}; @@ -481,6 +503,34 @@ mod tests { assert_poll_next_none!(pl); } + #[actix_rt::test] + async fn mut_equivalence() { + assert_eq!(().size(), BodySize::Sized(0)); + assert_eq!(().size(), (&(&mut ())).size()); + + let pl = &mut (); + pin!(pl); + assert_poll_next_none!(pl); + + let pl = &mut Box::new(()); + pin!(pl); + assert_poll_next_none!(pl); + + let mut body = body::SizedStream::new( + 8, + stream::iter([ + Ok::<_, std::io::Error>(Bytes::from("1234")), + Ok(Bytes::from("5678")), + ]), + ); + let body = &mut body; + assert_eq!(body.size(), BodySize::Sized(8)); + pin!(body); + assert_poll_next!(body, Bytes::from_static(b"1234")); + assert_poll_next!(body, Bytes::from_static(b"5678")); + assert_poll_next_none!(body); + } + #[allow(clippy::let_unit_value)] #[actix_rt::test] async fn test_unit() { @@ -607,4 +657,18 @@ mod tests { let not_body = resp_body.downcast_ref::<()>(); assert!(not_body.is_none()); } + + #[actix_rt::test] + async fn non_owning_to_bytes() { + let mut body = BoxBody::new(()); + let bytes = body::to_bytes(&mut body).await.unwrap(); + assert_eq!(bytes, Bytes::new()); + + let mut body = body::BodyStream::new(stream::iter([ + Ok::<_, std::io::Error>(Bytes::from("1234")), + Ok(Bytes::from("5678")), + ])); + let bytes = body::to_bytes(&mut body).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"12345678")); + } } From bd5c0af0a6bf9a720cf0685b3334151de1d43c32 Mon Sep 17 00:00:00 2001 From: e-rhodes <33500135+e-rhodes@users.noreply.github.com> Date: Thu, 15 Sep 2022 07:06:34 -0600 Subject: [PATCH 512/861] Add ability to set default error handlers to the `ErrorHandler` middleware (#2784) Co-authored-by: erhodes Co-authored-by: Rob Ede --- actix-web/CHANGES.md | 2 + actix-web/src/middleware/err_handlers.rs | 295 ++++++++++++++++++++++- 2 files changed, 291 insertions(+), 6 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 9ded67e35..00ab6c9c8 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -3,7 +3,9 @@ ## Unreleased - 2022-xx-xx ### Added - Add `ContentDisposition::attachment` constructor. [#2867] +- Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] +[#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs index f74220cd2..3a4e44a2c 100644 --- a/actix-web/src/middleware/err_handlers.rs +++ b/actix-web/src/middleware/err_handlers.rs @@ -30,11 +30,25 @@ pub enum ErrorHandlerResponse { type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; +type DefaultHandler = Option>>; + /// Middleware for registering custom status code based error handlers. /// -/// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler +/// Register handlers with the [`ErrorHandlers::handler()`] method to register a custom error handler /// for a given status code. Handlers can modify existing responses or create completely new ones. /// +/// To register a default handler, use the [`ErrorHandlers::default_handler()`] method. This +/// handler will be used only if a response has an error status code (400-599) that isn't covered by +/// a more specific handler (set with the [`handler()`][ErrorHandlers::handler] method). See examples +/// below. +/// +/// To register a default for only client errors (400-499) or only server errors (500-599), use the +/// [`ErrorHandlers::default_handler_client()`] and [`ErrorHandlers::default_handler_server()`] +/// methods, respectively. +/// +/// Any response with a status code that isn't covered by a specific handler or a default handler +/// will pass by unchanged by this middleware. +/// /// # Examples /// ``` /// use actix_web::http::{header, StatusCode}; @@ -53,7 +67,70 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result(mut res: dev::ServiceResponse) -> Result> { +/// res.response_mut().headers_mut().insert( +/// header::CONTENT_TYPE, +/// header::HeaderValue::from_static("Error"), +/// ); +/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) +/// } +/// +/// fn handle_bad_request(mut res: dev::ServiceResponse) -> Result> { +/// res.response_mut().headers_mut().insert( +/// header::CONTENT_TYPE, +/// header::HeaderValue::from_static("Bad Request Error"), +/// ); +/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) +/// } +/// +/// // Bad Request errors will hit `handle_bad_request()`, while all other errors will hit +/// // `add_error_header()`. The order in which the methods are called is not meaningful. +/// let app = App::new() +/// .wrap( +/// ErrorHandlers::new() +/// .default_handler(add_error_header) +/// .handler(StatusCode::BAD_REQUEST, handle_bad_request) +/// ) +/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError))); +/// ``` +/// Alternatively, you can set default handlers for only client or only server errors: +/// +/// ```rust +/// # use actix_web::http::{header, StatusCode}; +/// # use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; +/// # use actix_web::{dev, web, App, HttpResponse, Result}; +/// # fn add_error_header(mut res: dev::ServiceResponse) -> Result> { +/// # res.response_mut().headers_mut().insert( +/// # header::CONTENT_TYPE, +/// # header::HeaderValue::from_static("Error"), +/// # ); +/// # Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) +/// # } +/// # fn handle_bad_request(mut res: dev::ServiceResponse) -> Result> { +/// # res.response_mut().headers_mut().insert( +/// # header::CONTENT_TYPE, +/// # header::HeaderValue::from_static("Bad Request Error"), +/// # ); +/// # Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) +/// # } +/// // Bad request errors will hit `handle_bad_request()`, other client errors will hit +/// // `add_error_header()`, and server errors will pass through unchanged +/// let app = App::new() +/// .wrap( +/// ErrorHandlers::new() +/// .default_handler_client(add_error_header) // or .default_handler_server +/// .handler(StatusCode::BAD_REQUEST, handle_bad_request) +/// ) +/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError))); +/// ``` pub struct ErrorHandlers { + default_client: DefaultHandler, + default_server: DefaultHandler, handlers: Handlers, } @@ -62,6 +139,8 @@ type Handlers = Rc>>>; impl Default for ErrorHandlers { fn default() -> Self { ErrorHandlers { + default_client: Default::default(), + default_server: Default::default(), handlers: Default::default(), } } @@ -83,6 +162,66 @@ impl ErrorHandlers { .insert(status, Box::new(handler)); self } + + /// Register a default error handler. + /// + /// Any request with a status code that hasn't been given a specific other handler (by calling + /// [`.handler()`][ErrorHandlers::handler]) will fall back on this. + /// + /// Note that this will overwrite any default handlers previously set by calling + /// [`.default_handler_client()`][ErrorHandlers::default_handler_client] or + /// [`.default_handler_server()`][ErrorHandlers::default_handler_server], but not any set by + /// calling [`.handler()`][ErrorHandlers::handler]. + pub fn default_handler(self, handler: F) -> Self + where + F: Fn(ServiceResponse) -> Result> + 'static, + { + let handler = Rc::new(handler); + Self { + default_server: Some(handler.clone()), + default_client: Some(handler), + ..self + } + } + + /// Register a handler on which to fall back for client error status codes (400-499). + pub fn default_handler_client(self, handler: F) -> Self + where + F: Fn(ServiceResponse) -> Result> + 'static, + { + Self { + default_client: Some(Rc::new(handler)), + ..self + } + } + + /// Register a handler on which to fall back for server error status codes (500-599). + pub fn default_handler_server(self, handler: F) -> Self + where + F: Fn(ServiceResponse) -> Result> + 'static, + { + Self { + default_server: Some(Rc::new(handler)), + ..self + } + } + + /// Selects the most appropriate handler for the given status code. + /// + /// If the `handlers` map has an entry for that status code, that handler is returned. + /// Otherwise, fall back on the appropriate default handler. + fn get_handler<'a>( + status: &StatusCode, + default_client: Option<&'a ErrorHandler>, + default_server: Option<&'a ErrorHandler>, + handlers: &'a Handlers, + ) -> Option<&'a ErrorHandler> { + handlers + .get(status) + .map(|h| h.as_ref()) + .or_else(|| status.is_client_error().then(|| default_client).flatten()) + .or_else(|| status.is_server_error().then(|| default_server).flatten()) + } } impl Transform for ErrorHandlers @@ -99,13 +238,24 @@ where fn new_transform(&self, service: S) -> Self::Future { let handlers = self.handlers.clone(); - Box::pin(async move { Ok(ErrorHandlersMiddleware { service, handlers }) }) + let default_client = self.default_client.clone(); + let default_server = self.default_server.clone(); + Box::pin(async move { + Ok(ErrorHandlersMiddleware { + service, + default_client, + default_server, + handlers, + }) + }) } } #[doc(hidden)] pub struct ErrorHandlersMiddleware { service: S, + default_client: DefaultHandler, + default_server: DefaultHandler, handlers: Handlers, } @@ -123,8 +273,15 @@ where fn call(&self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); + let default_client = self.default_client.clone(); + let default_server = self.default_server.clone(); let fut = self.service.call(req); - ErrorHandlersFuture::ServiceFuture { fut, handlers } + ErrorHandlersFuture::ServiceFuture { + fut, + default_client, + default_server, + handlers, + } } } @@ -137,6 +294,8 @@ pin_project! { ServiceFuture { #[pin] fut: Fut, + default_client: DefaultHandler, + default_server: DefaultHandler, handlers: Handlers, }, ErrorHandlerFuture { @@ -153,10 +312,22 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project() { - ErrorHandlersProj::ServiceFuture { fut, handlers } => { + ErrorHandlersProj::ServiceFuture { + fut, + default_client, + default_server, + handlers, + } => { let res = ready!(fut.poll(cx))?; + let status = res.status(); - match handlers.get(&res.status()) { + let handler = ErrorHandlers::get_handler( + &status, + default_client.as_mut().map(|f| Rc::as_ref(f)), + default_server.as_mut().map(|f| Rc::as_ref(f)), + handlers, + ); + match handler { Some(handler) => match handler(res)? { ErrorHandlerResponse::Response(res) => Poll::Ready(Ok(res)), ErrorHandlerResponse::Future(fut) => { @@ -166,7 +337,6 @@ where self.poll(cx) } }, - None => Poll::Ready(Ok(res.map_into_left_body())), } } @@ -298,4 +468,117 @@ mod tests { "error in error handler" ); } + + #[actix_rt::test] + async fn default_error_handler() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler(mut res: ServiceResponse) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + let make_mw = |status| async move { + ErrorHandlers::new() + .default_handler(error_handler) + .new_transform(test::status_service(status).into_service()) + .await + .unwrap() + }; + let mw_server = make_mw(StatusCode::INTERNAL_SERVER_ERROR).await; + let mw_client = make_mw(StatusCode::BAD_REQUEST).await; + + let resp = + test::call_service(&mw_client, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = + test::call_service(&mw_server, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } + + #[actix_rt::test] + async fn default_handlers_separate_client_server() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler_client( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + #[allow(clippy::unnecessary_wraps)] + fn error_handler_server( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0002")); + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + let make_mw = |status| async move { + ErrorHandlers::new() + .default_handler_server(error_handler_server) + .default_handler_client(error_handler_client) + .new_transform(test::status_service(status).into_service()) + .await + .unwrap() + }; + let mw_server = make_mw(StatusCode::INTERNAL_SERVER_ERROR).await; + let mw_client = make_mw(StatusCode::BAD_REQUEST).await; + + let resp = + test::call_service(&mw_client, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = + test::call_service(&mw_server, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + } + + #[actix_rt::test] + async fn default_handlers_specialization() { + #[allow(clippy::unnecessary_wraps)] + fn error_handler_client( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + #[allow(clippy::unnecessary_wraps)] + fn error_handler_specific( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0003")); + Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) + } + + let make_mw = |status| async move { + ErrorHandlers::new() + .default_handler_client(error_handler_client) + .handler(StatusCode::UNPROCESSABLE_ENTITY, error_handler_specific) + .new_transform(test::status_service(status).into_service()) + .await + .unwrap() + }; + let mw_client = make_mw(StatusCode::BAD_REQUEST).await; + let mw_specific = make_mw(StatusCode::UNPROCESSABLE_ENTITY).await; + + let resp = + test::call_service(&mw_client, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = + test::call_service(&mw_specific, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0003"); + } } From 07a729043211e819a8494d53f01c50445cf491f2 Mon Sep 17 00:00:00 2001 From: Torin Cooper-Bennun <40573959+tcbennun@users.noreply.github.com> Date: Mon, 19 Sep 2022 18:44:52 +0100 Subject: [PATCH 513/861] Fix typo in error string for i32 parse in router deserialization (#2876) * fix typo in error string for i32 parse * update actix-router changelog for #2876 * Update CHANGES.md Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 3 +++ actix-router/src/de.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index ec4676ea3..45db615e8 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2022-xx-xx +- Fix typo in error string in `Deserializer::deserialize_i32` implementation for `Value`. [#2876] - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. +[#2876]: https://github.com/actix/actix-web/pull/2876 + ## 0.5.0 - 2022-02-22 ### Added diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index 55fcdc912..458e08930 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -293,7 +293,7 @@ impl<'de> Deserializer<'de> for Value<'de> { parse_value!(deserialize_bool, visit_bool, "bool"); parse_value!(deserialize_i8, visit_i8, "i8"); parse_value!(deserialize_i16, visit_i16, "i16"); - parse_value!(deserialize_i32, visit_i32, "i16"); + parse_value!(deserialize_i32, visit_i32, "i32"); parse_value!(deserialize_i64, visit_i64, "i64"); parse_value!(deserialize_u8, visit_u8, "u8"); parse_value!(deserialize_u16, visit_u16, "u16"); From 894effb8569e53227e98480fd72becc04ea3131d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Sep 2022 18:52:16 +0100 Subject: [PATCH 514/861] prepare actix-router release 0.5.1 --- actix-router/CHANGES.md | 5 ++++- actix-router/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 45db615e8..51e7cbc10 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,7 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx -- Fix typo in error string in `Deserializer::deserialize_i32` implementation for `Value`. [#2876] + + +## 0.5.1 - 2022-09-19 +- Correct typo in error string for `i32` deserialization. [#2876] - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. [#2876]: https://github.com/actix/actix-web/pull/2876 diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 141b2d39e..19d6abc62 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.5.0" +version = "0.5.1" authors = [ "Nikolay Kim ", "Ali MJ Al-Nasrawy ", diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a89dde2ee..2477364a6 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" proc-macro = true [dependencies] -actix-router = "0.5.0" +actix-router = "0.5" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "extra-traits"] } From 4d3689db5e0dc1f7321994d4405b116094b949b6 Mon Sep 17 00:00:00 2001 From: e-rhodes <33500135+e-rhodes@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:17:58 -0600 Subject: [PATCH 515/861] Remove unnecesary clones in extractor configs (#2884) Co-authored-by: erhodes Co-authored-by: Rob Ede --- actix-web/src/types/json.rs | 2 +- actix-web/src/types/payload.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web/src/types/json.rs b/actix-web/src/types/json.rs index 8fdbfafa4..4eab55175 100644 --- a/actix-web/src/types/json.rs +++ b/actix-web/src/types/json.rs @@ -290,7 +290,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig { impl Default for JsonConfig { fn default() -> Self { - DEFAULT_CONFIG.clone() + DEFAULT_CONFIG } } diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs index af195b867..f17a4ed6d 100644 --- a/actix-web/src/types/payload.rs +++ b/actix-web/src/types/payload.rs @@ -271,7 +271,7 @@ const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { impl Default for PayloadConfig { fn default() -> Self { - DEFAULT_CONFIG.clone() + DEFAULT_CONFIG } } From ef64d6a27cb57853a62739935a7d124d3500947f Mon Sep 17 00:00:00 2001 From: david-monroe <112688821+david-monroe@users.noreply.github.com> Date: Fri, 23 Sep 2022 14:39:18 +0200 Subject: [PATCH 516/861] update derive_more dependency to 0.99.8 (#2888) --- actix-web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index d57e52f36..c7b54bd46 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -80,7 +80,7 @@ bytes = "1" bytestring = "1" cfg-if = "1" cookie = { version = "0.16", features = ["percent-encode"], optional = true } -derive_more = "0.99.5" +derive_more = "0.99.8" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } From fd6330585978d949627fcdca42a6896031b7fed5 Mon Sep 17 00:00:00 2001 From: Jacob Halsey Date: Fri, 23 Sep 2022 18:06:40 +0100 Subject: [PATCH 517/861] Fix actix-multipart field content_type() to return an Option (#2885) Co-authored-by: Rob Ede --- actix-multipart/CHANGES.md | 3 +++ actix-multipart/src/server.rs | 47 +++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index d0da40fb4..655487e54 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased - 2022-xx-xx - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. +- `Field::content_type()` now returns `Option<&mime::Mime>` [#2880] + +[#2880]: https://github.com/actix/actix-web/pull/2880 ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 239f7f905..6985cb56d 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -361,17 +361,18 @@ impl InnerMultipart { return Poll::Ready(Some(Err(MultipartError::NoContentDisposition))); }; - let ct: mime::Mime = headers + let ct: Option = headers .get(&header::CONTENT_TYPE) .and_then(|ct| ct.to_str().ok()) - .and_then(|ct| ct.parse().ok()) - .unwrap_or(mime::APPLICATION_OCTET_STREAM); + .and_then(|ct| ct.parse().ok()); self.state = InnerState::Boundary; // nested multipart stream is not supported - if ct.type_() == mime::MULTIPART { - return Poll::Ready(Some(Err(MultipartError::Nested))); + if let Some(mime) = &ct { + if mime.type_() == mime::MULTIPART { + return Poll::Ready(Some(Err(MultipartError::Nested))); + } } let field = @@ -399,7 +400,7 @@ impl Drop for InnerMultipart { /// A single field in a multipart stream pub struct Field { - ct: mime::Mime, + ct: Option, cd: ContentDisposition, headers: HeaderMap, inner: Rc>, @@ -410,7 +411,7 @@ impl Field { fn new( safety: Safety, headers: HeaderMap, - ct: mime::Mime, + ct: Option, cd: ContentDisposition, inner: Rc>, ) -> Self { @@ -428,9 +429,13 @@ impl Field { &self.headers } - /// Returns a reference to the field's content (mime) type. - pub fn content_type(&self) -> &mime::Mime { - &self.ct + /// Returns a reference to the field's content (mime) type, if it is supplied by the client. + /// + /// According to [RFC 7578](https://www.rfc-editor.org/rfc/rfc7578#section-4.4), if it is not + /// present, it should default to "text/plain". Note it is the responsibility of the client to + /// provide the appropriate content type, there is no attempt to validate this by the server. + pub fn content_type(&self) -> Option<&mime::Mime> { + self.ct.as_ref() } /// Returns the field's Content-Disposition. @@ -482,7 +487,11 @@ impl Stream for Field { impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\nField: {}", self.ct)?; + if let Some(ct) = &self.ct { + writeln!(f, "\nField: {}", ct)?; + } else { + writeln!(f, "\nField:")?; + } writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; writeln!(f, " headers:")?; for (key, val) in self.headers.iter() { @@ -1024,8 +1033,8 @@ mod tests { assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + assert_eq!(field.content_type().unwrap().type_(), mime::TEXT); + assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN); match field.next().await.unwrap() { Ok(chunk) => assert_eq!(chunk, "test"), @@ -1041,8 +1050,8 @@ mod tests { match multipart.next().await.unwrap() { Ok(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + assert_eq!(field.content_type().unwrap().type_(), mime::TEXT); + assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN); match field.next().await { Some(Ok(chunk)) => assert_eq!(chunk, "data"), @@ -1086,8 +1095,8 @@ mod tests { assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + assert_eq!(field.content_type().unwrap().type_(), mime::TEXT); + assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN); assert_eq!(get_whole_field(&mut field).await, "test"); } @@ -1096,8 +1105,8 @@ mod tests { match multipart.next().await { Some(Ok(mut field)) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + assert_eq!(field.content_type().unwrap().type_(), mime::TEXT); + assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN); assert_eq!(get_whole_field(&mut field).await, "data"); } From 172c4c7a0a7621e00b45838c8af29b6eaae79b1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 25 Sep 2022 15:32:26 +0100 Subject: [PATCH 518/861] use noop hasher in extensions (#2890) --- actix-http/CHANGES.md | 4 ++++ actix-http/src/extensions.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 816d4b71f..045ae461f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -5,7 +5,11 @@ - Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868] - Implement `MessageBody` for `Pin` where `B::Target: MessageBody`. [#2868] +### Performance +- Improve overall performance of operations on `Extensions`. [#2890] + [#2868]: https://github.com/actix/actix-web/pull/2868 +[#2890]: https://github.com/actix/actix-web/pull/2890 ## 3.2.2 - 2022-09-11 diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 60b769d13..f2047a9ce 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,9 +1,30 @@ use std::{ any::{Any, TypeId}, + collections::HashMap, fmt, + hash::{BuildHasherDefault, Hasher}, }; -use ahash::AHashMap; +/// A hasher for `TypeId`s that takes advantage of its known characteristics. +/// +/// Author of `anymap` crate has done research on the topic: +/// https://github.com/chris-morgan/anymap/blob/2e9a5704/src/lib.rs#L599 +#[derive(Debug, Default)] +struct NoOpHasher(u64); + +impl Hasher for NoOpHasher { + fn write(&mut self, _bytes: &[u8]) { + unimplemented!("This NoOpHasher can only handle u64s") + } + + fn write_u64(&mut self, i: u64) { + self.0 = i; + } + + fn finish(&self) -> u64 { + self.0 + } +} /// A type map for request extensions. /// @@ -11,7 +32,7 @@ use ahash::AHashMap; #[derive(Default)] pub struct Extensions { /// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys. - map: AHashMap>, + map: HashMap, BuildHasherDefault>, } impl Extensions { @@ -19,7 +40,7 @@ impl Extensions { #[inline] pub fn new() -> Extensions { Extensions { - map: AHashMap::new(), + map: HashMap::default(), } } From cc7145d41dd75beb61ee1882ba677c20d5f1fd76 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 25 Sep 2022 20:54:17 +0100 Subject: [PATCH 519/861] rust 1.64 clippy run (#2891) --- actix-files/src/service.rs | 2 +- actix-http/src/body/message_body.rs | 2 +- actix-http/src/body/utils.rs | 2 +- actix-http/src/h1/dispatcher_tests.rs | 4 ++-- actix-http/src/header/map.rs | 2 +- actix-http/src/requests/request.rs | 4 ++-- actix-http/src/responses/response.rs | 4 ++-- actix-http/src/ws/frame.rs | 8 ++++---- actix-http/src/ws/proto.rs | 2 +- actix-http/tests/test_rustls.rs | 2 +- actix-multipart/src/server.rs | 14 ++++++-------- actix-router/src/path.rs | 1 + actix-router/src/resource.rs | 18 +++++++++--------- actix-web/src/app.rs | 2 +- actix-web/src/app_service.rs | 2 +- actix-web/src/config.rs | 2 +- actix-web/src/http/header/cache_control.rs | 2 +- actix-web/src/request.rs | 12 ++++++------ actix-web/src/response/builder.rs | 2 +- actix-web/src/rmap.rs | 12 ++++++------ actix-web/src/scope.rs | 5 ++--- awc/src/client/pool.rs | 2 +- awc/src/ws.rs | 4 ++-- awc/tests/test_client.rs | 15 ++++----------- 24 files changed, 58 insertions(+), 67 deletions(-) diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index ec09af01c..d94fd5850 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -23,7 +23,7 @@ impl Deref for FilesService { type Target = FilesServiceInner; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index bac2fece1..0cfaa8653 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -131,7 +131,7 @@ mod foreign_impls { type Error = B::Error; fn size(&self) -> BodySize { - (&**self).size() + (**self).size() } fn poll_next( diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs index 194af47f8..0a6fb0c15 100644 --- a/actix-http/src/body/utils.rs +++ b/actix-http/src/body/utils.rs @@ -42,7 +42,7 @@ pub async fn to_bytes(body: B) -> Result { let body = body.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), + Some(Ok(bytes)) => buf.extend_from_slice(&bytes), None => return Poll::Ready(Ok(())), Some(Err(err)) => return Poll::Ready(Err(err)), } diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index b3ee3d2bb..3eea859bf 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -637,7 +637,7 @@ async fn expect_handling() { if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); + let mut res = io.write_buf()[..].to_owned(); stabilize_date_header(&mut res); assert_eq!( @@ -699,7 +699,7 @@ async fn expect_eager() { if let DispatcherState::Normal { ref inner } = h1.inner { let io = inner.io.as_ref().unwrap(); - let mut res = (&io.write_buf()[..]).to_owned(); + let mut res = io.write_buf()[..].to_owned(); stabilize_date_header(&mut res); // Despite the content-length header and even though the request payload has not diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 8f6d1cead..28906e835 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -309,7 +309,7 @@ impl HeaderMap { pub fn get_all(&self, key: impl AsHeaderName) -> std::slice::Iter<'_, HeaderValue> { match self.get_value(key) { Some(value) => value.iter(), - None => (&[]).iter(), + None => [].iter(), } } diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs index 0f8e78d46..ac358e8df 100644 --- a/actix-http/src/requests/request.rs +++ b/actix-http/src/requests/request.rs @@ -113,14 +113,14 @@ impl

Request

{ #[inline] /// Http message part of the request pub fn head(&self) -> &RequestHead { - &*self.head + &self.head } #[inline] #[doc(hidden)] /// Mutable reference to a HTTP message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { - &mut *self.head + &mut self.head } /// Mutable reference to the message's headers. diff --git a/actix-http/src/responses/response.rs b/actix-http/src/responses/response.rs index ceb158f65..03908d9ce 100644 --- a/actix-http/src/responses/response.rs +++ b/actix-http/src/responses/response.rs @@ -83,13 +83,13 @@ impl Response { /// Returns a reference to the head of this response. #[inline] pub fn head(&self) -> &ResponseHead { - &*self.head + &self.head } /// Returns a mutable reference to the head of this response. #[inline] pub fn head_mut(&mut self) -> &mut ResponseHead { - &mut *self.head + &mut self.head } /// Returns the status code of this response. diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 3659b6c3b..c7e0427ea 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -313,7 +313,7 @@ mod tests { #[test] fn test_parse_frame_no_mask() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - buf.extend(&[1u8]); + buf.extend([1u8]); assert!(Parser::parse(&mut buf, true, 1024).is_err()); @@ -326,7 +326,7 @@ mod tests { #[test] fn test_parse_frame_max_size() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); - buf.extend(&[1u8, 1u8]); + buf.extend([1u8, 1u8]); assert!(Parser::parse(&mut buf, true, 1).is_err()); @@ -340,9 +340,9 @@ mod tests { fn test_parse_frame_max_size_recoverability() { let mut buf = BytesMut::new(); // The first text frame with length == 2, payload doesn't matter. - buf.extend(&[0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]); + buf.extend([0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]); // Next binary frame with length == 2 and payload == `[0x1111_1111u8, 0x1111_1111u8]`. - buf.extend(&[0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]); + buf.extend([0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]); assert_eq!(buf.len(), 8); assert!(matches!( diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 01fe9dd3c..7222168b7 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -244,7 +244,7 @@ pub fn hash_key(key: &[u8]) -> [u8; 28] { }; let mut hash_b64 = [0; 28]; - let n = base64::encode_config_slice(&hash, base64::STANDARD, &mut hash_b64); + let n = base64::encode_config_slice(hash, base64::STANDARD, &mut hash_b64); assert_eq!(n, 28); hash_b64 diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 2bbf1524b..d9ff42b7d 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -41,7 +41,7 @@ where let body = stream.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), + Some(Ok(bytes)) => buf.extend_from_slice(&bytes), None => return Poll::Ready(Ok(())), Some(Err(err)) => return Poll::Ready(Err(err)), } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 6985cb56d..c3757177f 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -289,10 +289,8 @@ impl InnerMultipart { match self.state { // read until first boundary InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary( - &mut *payload, - &self.boundary, - )? { + match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? + { Some(eof) => { if eof { self.state = InnerState::Eof; @@ -306,7 +304,7 @@ impl InnerMultipart { } // read boundary InnerState::Boundary => { - match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? { + match InnerMultipart::read_boundary(&mut payload, &self.boundary)? { None => return Poll::Pending, Some(eof) => { if eof { @@ -323,7 +321,7 @@ impl InnerMultipart { // read field headers for next field if self.state == InnerState::Headers { - if let Some(headers) = InnerMultipart::read_headers(&mut *payload)? { + if let Some(headers) = InnerMultipart::read_headers(&mut payload)? { self.state = InnerState::Boundary; headers } else { @@ -652,9 +650,9 @@ impl InnerField { let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) { if !self.eof { let res = if let Some(ref mut len) = self.length { - InnerField::read_len(&mut *payload, len) + InnerField::read_len(&mut payload, len) } else { - InnerField::read_stream(&mut *payload, &self.boundary) + InnerField::read_stream(&mut payload, &self.boundary) }; match res { diff --git a/actix-router/src/path.rs b/actix-router/src/path.rs index 5eef1c1e7..34dabcfbe 100644 --- a/actix-router/src/path.rs +++ b/actix-router/src/path.rs @@ -242,6 +242,7 @@ mod tests { use super::*; + #[allow(clippy::needless_borrow)] #[test] fn deref_impls() { let mut foo = Path::new("/foo"); diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 3c6754aeb..f198115ad 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -1503,31 +1503,31 @@ mod tests { fn build_path_list() { let mut s = String::new(); let resource = ResourceDef::new("/user/{item1}/test"); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["user1"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter())); assert_eq!(s, "/user/user1/test"); let mut s = String::new(); let resource = ResourceDef::new("/user/{item1}/{item2}/test"); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter())); assert_eq!(s, "/user/item/item2/test"); let mut s = String::new(); let resource = ResourceDef::new("/user/{item1}/{item2}"); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter())); assert_eq!(s, "/user/item/item2"); let mut s = String::new(); let resource = ResourceDef::new("/user/{item1}/{item2}/"); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter())); assert_eq!(s, "/user/item/item2/"); let mut s = String::new(); - assert!(!resource.resource_path_from_iter(&mut s, &mut (&["item"]).iter())); + assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter())); let mut s = String::new(); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["item", "item2"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter())); assert_eq!(s, "/user/item/item2/"); - assert!(!resource.resource_path_from_iter(&mut s, &mut (&["item"]).iter())); + assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter())); let mut s = String::new(); assert!(resource.resource_path_from_iter(&mut s, &mut vec!["item", "item2"].iter())); @@ -1604,10 +1604,10 @@ mod tests { let resource = ResourceDef::new("/user/{item1}*"); let mut s = String::new(); - assert!(!resource.resource_path_from_iter(&mut s, &mut (&[""; 0]).iter())); + assert!(!resource.resource_path_from_iter(&mut s, &mut [""; 0].iter())); let mut s = String::new(); - assert!(resource.resource_path_from_iter(&mut s, &mut (&["user1"]).iter())); + assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter())); assert_eq!(s, "/user/user1"); let mut s = String::new(); diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 213c8beff..ec37ff8a4 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -682,7 +682,7 @@ mod tests { "/test", web::get().to(|req: HttpRequest| { HttpResponse::Ok() - .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) + .body(req.url_for("youtube", ["12345"]).unwrap().to_string()) }), ), ) diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs index 28ff8c614..0b5ba2ab6 100644 --- a/actix-web/src/app_service.rs +++ b/actix-web/src/app_service.rs @@ -173,7 +173,7 @@ impl AppInitServiceState { #[inline] pub(crate) fn rmap(&self) -> &ResourceMap { - &*self.rmap + &self.rmap } #[inline] diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index 58a099c75..68bea34ca 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -344,7 +344,7 @@ mod tests { "/test", web::get().to(|req: HttpRequest| { HttpResponse::Ok() - .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) + .body(req.url_for("youtube", ["12345"]).unwrap().to_string()) }), ), ) diff --git a/actix-web/src/http/header/cache_control.rs b/actix-web/src/http/header/cache_control.rs index 490d36558..37629313e 100644 --- a/actix-web/src/http/header/cache_control.rs +++ b/actix-web/src/http/header/cache_control.rs @@ -176,7 +176,7 @@ impl str::FromStr for CacheDirective { _ => match s.find('=') { Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { + match (&s[..idx], s[idx + 1..].trim_matches('"')) { ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index e2a9bd4e5..6a32bf838 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -219,7 +219,7 @@ impl HttpRequest { /// for urls that do not contain variable parts. pub fn url_for_static(&self, name: &str) -> Result { const NO_PARAMS: [&str; 0] = []; - self.url_for(name, &NO_PARAMS) + self.url_for(name, NO_PARAMS) } /// Get a reference to a `ResourceMap` of current application. @@ -306,7 +306,7 @@ impl HttpRequest { #[inline] fn app_state(&self) -> &AppInitServiceState { - &*self.inner.app_state + &self.inner.app_state } /// Load request cookies. @@ -583,14 +583,14 @@ mod tests { .to_http_request(); assert_eq!( - req.url_for("unknown", &["test"]), + req.url_for("unknown", ["test"]), Err(UrlGenerationError::ResourceNotFound) ); assert_eq!( - req.url_for("index", &["test"]), + req.url_for("index", ["test"]), Err(UrlGenerationError::NotEnoughElements) ); - let url = req.url_for("index", &["test", "html"]); + let url = req.url_for("index", ["test", "html"]); assert_eq!( url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html" @@ -646,7 +646,7 @@ mod tests { rmap.add(&mut rdef, None); let req = TestRequest::default().rmap(rmap).to_http_request(); - let url = req.url_for("youtube", &["oHg5SJYRHA0"]); + let url = req.url_for("youtube", ["oHg5SJYRHA0"]); assert_eq!( url.ok().unwrap().as_str(), "https://youtube.com/watch/oHg5SJYRHA0" diff --git a/actix-web/src/response/builder.rs b/actix-web/src/response/builder.rs index f50aad9f4..120d4c358 100644 --- a/actix-web/src/response/builder.rs +++ b/actix-web/src/response/builder.rs @@ -457,7 +457,7 @@ mod tests { assert_eq!(ct, HeaderValue::from_static("application/json")); assert_body_eq!(res, br#"["v1","v2","v3"]"#); - let res = HttpResponse::Ok().json(&["v1", "v2", "v3"]); + let res = HttpResponse::Ok().json(["v1", "v2", "v3"]); let ct = res.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_body_eq!(res, br#"["v1","v2","v3"]"#); diff --git a/actix-web/src/rmap.rs b/actix-web/src/rmap.rs index 6a1a187b2..6e10717c3 100644 --- a/actix-web/src/rmap.rs +++ b/actix-web/src/rmap.rs @@ -449,12 +449,12 @@ mod tests { let req = req.to_http_request(); let url = rmap - .url_for(&req, "post", &["u123", "foobar"]) + .url_for(&req, "post", ["u123", "foobar"]) .unwrap() .to_string(); assert_eq!(url, "http://localhost:8888/user/u123/post/foobar"); - assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); + assert!(rmap.url_for(&req, "missing", ["u123"]).is_err()); } #[test] @@ -490,7 +490,7 @@ mod tests { assert_eq!(url.path(), OUTPUT); assert!(rmap.url_for(&req, "external.2", INPUT).is_err()); - assert!(rmap.url_for(&req, "external.2", &[""]).is_err()); + assert!(rmap.url_for(&req, "external.2", [""]).is_err()); } #[test] @@ -524,7 +524,7 @@ mod tests { let req = req.to_http_request(); assert_eq!( - rmap.url_for(&req, "duck", &["abcd"]).unwrap().to_string(), + rmap.url_for(&req, "duck", ["abcd"]).unwrap().to_string(), "https://duck.com/abcd" ); } @@ -552,9 +552,9 @@ mod tests { let req = crate::test::TestRequest::default().to_http_request(); - let url = rmap.url_for(&req, "nested", &[""; 0]).unwrap().to_string(); + let url = rmap.url_for(&req, "nested", [""; 0]).unwrap().to_string(); assert_eq!(url, "http://localhost:8080/bar/nested"); - assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); + assert!(rmap.url_for(&req, "missing", ["u123"]).is_err()); } } diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index 07eb1093a..9af05674b 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -1133,7 +1133,7 @@ mod tests { "/", web::get().to(|req: HttpRequest| { HttpResponse::Ok() - .body(req.url_for("youtube", &["xxxxxx"]).unwrap().to_string()) + .body(req.url_for("youtube", ["xxxxxx"]).unwrap().to_string()) }), ); })); @@ -1152,8 +1152,7 @@ mod tests { 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() - .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap())) }), )), ))) diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs index cc3e4d7c0..5655b5845 100644 --- a/awc/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -97,7 +97,7 @@ where type Target = ConnectionPoolInnerPriv; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index d8ed4c879..b316f68b4 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -321,7 +321,7 @@ impl WebsocketsRequest { // Generate a random key for the `Sec-WebSocket-Key` header which is a base64-encoded // (see RFC 4648 §4) value that, when decoded, is 16 bytes in length (RFC 6455 §1.3). let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); + let key = base64::encode(sec_key); self.head.headers.insert( header::SEC_WEBSOCKET_KEY, @@ -513,7 +513,7 @@ mod tests { .origin("test-origin") .max_frame_size(100) .server_mode() - .protocols(&["v1", "v2"]) + .protocols(["v1", "v2"]) .set_header_if_none(header::CONTENT_TYPE, "json") .set_header_if_none(header::CONTENT_TYPE, "text") .cookie(Cookie::build("cookie1", "value1").finish()); diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 165d8faf0..c4b468eeb 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -707,8 +707,7 @@ async fn client_cookie_handling() { async move { // Check cookies were sent correctly - let res: Result<(), Error> = req - .cookie("cookie1") + req.cookie("cookie1") .ok_or(()) .and_then(|c1| { if c1.value() == "value1" { @@ -725,16 +724,10 @@ async fn client_cookie_handling() { Err(()) } }) - .map_err(|_| Error::from(IoError::from(ErrorKind::NotFound))); + .map_err(|_| Error::from(IoError::from(ErrorKind::NotFound)))?; - if let Err(e) = res { - Err(e) - } else { - // Send some cookies back - Ok::<_, Error>( - HttpResponse::Ok().cookie(cookie1).cookie(cookie2).finish(), - ) - } + // Send some cookies back + Ok::<_, Error>(HttpResponse::Ok().cookie(cookie1).cookie(cookie2).finish()) } }), ) From 1519ae777273b926091add7865dc1ee30c5c35fe Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 26 Sep 2022 12:29:57 +0100 Subject: [PATCH 520/861] clarify tokio::main docs --- actix-web/src/rt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs index 929eadfd8..7973da73c 100644 --- a/actix-web/src/rt.rs +++ b/actix-web/src/rt.rs @@ -25,7 +25,7 @@ //! ``` //! //! # Running Actix Web Using `#[tokio::main]` -//! If you need to run something alongside Actix Web that uses Tokio's work stealing functionality, +//! If you need to run something that uses Tokio's work stealing functionality alongside Actix Web, //! you can run Actix Web under `#[tokio::main]`. The [`Server`](crate::dev::Server) object returned //! from [`HttpServer::run`](crate::HttpServer::run) can also be [`spawn`]ed, if preferred. //! From ad7e67f940f0ebd25e0ad2bf1642b65b92df8e18 Mon Sep 17 00:00:00 2001 From: Benny Nazimov <66024037+benny-n@users.noreply.github.com> Date: Mon, 26 Sep 2022 21:44:51 +0300 Subject: [PATCH 521/861] add `middleware::logger::custom_response_replace` (#2631) Co-authored-by: Rob Ede --- actix-web/CHANGES.md | 2 + actix-web/src/middleware/logger.rs | 186 +++++++++++++++++++++++++---- 2 files changed, 165 insertions(+), 23 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 00ab6c9c8..a018bc248 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,7 +4,9 @@ ### Added - Add `ContentDisposition::attachment` constructor. [#2867] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] +- Add `Logger::custom_response_replace()`. [#2631] +[#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 diff --git a/actix-web/src/middleware/logger.rs b/actix-web/src/middleware/logger.rs index 53a3550de..5fec5a013 100644 --- a/actix-web/src/middleware/logger.rs +++ b/actix-web/src/middleware/logger.rs @@ -26,7 +26,7 @@ use crate::{ body::{BodySize, MessageBody}, http::header::HeaderName, service::{ServiceRequest, ServiceResponse}, - Error, HttpResponse, Result, + Error, Result, }; /// Middleware for logging request and response summaries to the terminal. @@ -69,10 +69,11 @@ use crate::{ /// `%D` | Time taken to serve the request, in milliseconds /// `%U` | Request URL /// `%{r}a` | "Real IP" remote address **\*** -/// `%{FOO}i` | `request.headers["FOO"]` +/// `%{FOO}i` | `request.headers["FOO"]` /// `%{FOO}o` | `response.headers["FOO"]` /// `%{FOO}e` | `env_var["FOO"]` /// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO" +/// `%{FOO}xo` | [Custom response replacement](Logger::custom_response_replace) labelled "FOO" /// /// # Security /// **\*** "Real IP" remote address is calculated using @@ -179,6 +180,55 @@ impl Logger { self } + + /// Register a function that receives a `ServiceResponse` and returns a string for use in the + /// log line. + /// + /// The label passed as the first argument should match a replacement substring in + /// the logger format like `%{label}xo`. + /// + /// It is convention to print "-" to indicate no output instead of an empty string. + /// + /// The replacement function does not have access to the response body. + /// + /// # Examples + /// ``` + /// # use actix_web::{dev::ServiceResponse, middleware::Logger}; + /// fn log_if_error(res: &ServiceResponse) -> String { + /// if res.status().as_u16() >= 400 { + /// "ERROR".to_string() + /// } else { + /// "-".to_string() + /// } + /// } + /// + /// Logger::new("example %{ERROR_STATUS}xo") + /// .custom_response_replace("ERROR_STATUS", |res| log_if_error(res) ); + /// ``` + pub fn custom_response_replace( + mut self, + label: &str, + f: impl Fn(&ServiceResponse) -> String + 'static, + ) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + + let ft = inner.format.0.iter_mut().find( + |ft| matches!(ft, FormatText::CustomResponse(unit_label, _) if label == unit_label), + ); + + if let Some(FormatText::CustomResponse(_, res_fn)) = ft { + *res_fn = Some(CustomResponseFn { + inner_fn: Rc::new(f), + }); + } else { + debug!( + "Attempted to register custom response logging function for non-existent label: {}", + label + ); + } + + self + } } impl Default for Logger { @@ -210,10 +260,16 @@ where fn new_transform(&self, service: S) -> Self::Future { for unit in &self.0.format.0 { - // missing request replacement function diagnostic if let FormatText::CustomRequest(label, None) = unit { warn!( - "No custom request replacement function was registered for label \"{}\".", + "No custom request replacement function was registered for label: {}", + label + ); + } + + if let FormatText::CustomResponse(label, None) = unit { + warn!( + "No custom response replacement function was registered for label: {}", label ); } @@ -308,11 +364,25 @@ where debug!("Error in response: {:?}", error); } - if let Some(ref mut format) = this.format { + let res = if let Some(ref mut format) = this.format { + // to avoid polluting all the Logger types with the body parameter we swap the body + // out temporarily since it's not usable in custom response functions anyway + + let (req, res) = res.into_parts(); + let (res, body) = res.into_parts(); + + let temp_res = ServiceResponse::new(req, res.map_into_boxed_body()); + for unit in &mut format.0 { - unit.render_response(res.response()); + unit.render_response(&temp_res); } - } + + // re-construct original service response + let (req, res) = temp_res.into_parts(); + ServiceResponse::new(req, res.set_body(body)) + } else { + res + }; let time = *this.time; let format = this.format.take(); @@ -399,7 +469,7 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[%atPrUsbTD]?)").unwrap(); + let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|x[io])|[%atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -417,7 +487,7 @@ impl Format { if key.as_str() == "r" { FormatText::RealIpRemoteAddr } else { - unreachable!() + unreachable!("regex and code mismatch") } } "i" => { @@ -428,6 +498,7 @@ impl Format { } "e" => FormatText::EnvironHeader(key.as_str().to_owned()), "xi" => FormatText::CustomRequest(key.as_str().to_owned(), None), + "xo" => FormatText::CustomResponse(key.as_str().to_owned(), None), _ => unreachable!(), }) } else { @@ -475,6 +546,7 @@ enum FormatText { ResponseHeader(HeaderName), EnvironHeader(String), CustomRequest(String, Option), + CustomResponse(String, Option), } #[derive(Clone)] @@ -494,6 +566,23 @@ impl fmt::Debug for CustomRequestFn { } } +#[derive(Clone)] +struct CustomResponseFn { + inner_fn: Rc String>, +} + +impl CustomResponseFn { + fn call(&self, res: &ServiceResponse) -> String { + (self.inner_fn)(res) + } +} + +impl fmt::Debug for CustomResponseFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("custom_response_fn") + } +} + impl FormatText { fn render( &self, @@ -526,11 +615,12 @@ impl FormatText { } } - fn render_response(&mut self, res: &HttpResponse) { + fn render_response(&mut self, res: &ServiceResponse) { match self { FormatText::ResponseStatus => { *self = FormatText::Str(format!("{}", res.status().as_u16())) } + FormatText::ResponseHeader(ref name) => { let s = if let Some(val) = res.headers().get(name) { if let Ok(s) = val.to_str() { @@ -543,6 +633,16 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()) } + + FormatText::CustomResponse(_, res_fn) => { + let text = match res_fn { + Some(res_fn) => FormatText::Str(res_fn.call(res)), + None => FormatText::Str("-".to_owned()), + }; + + *self = text; + } + _ => {} } } @@ -627,8 +727,11 @@ mod tests { use actix_utils::future::ok; use super::*; - use crate::http::{header, StatusCode}; - use crate::test::{self, TestRequest}; + use crate::{ + http::{header, StatusCode}, + test::{self, TestRequest}, + HttpResponse, + }; #[actix_rt::test] async fn test_logger() { @@ -691,9 +794,10 @@ mod tests { unit.render_request(now, &req); } - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let req = TestRequest::default().to_http_request(); + let res = ServiceResponse::new(req, HttpResponse::Ok().finish()); for unit in &mut format.0 { - unit.render_response(&resp); + unit.render_response(&res); } let entry_time = OffsetDateTime::now_utc(); @@ -723,9 +827,10 @@ mod tests { unit.render_request(now, &req); } - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let req = TestRequest::default().to_http_request(); + let res = ServiceResponse::new(req, HttpResponse::Ok().force_close().finish()); for unit in &mut format.0 { - unit.render_response(&resp); + unit.render_response(&res); } let render = |fmt: &mut fmt::Formatter<'_>| { @@ -755,9 +860,10 @@ mod tests { unit.render_request(now, &req); } - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let req = TestRequest::default().to_http_request(); + let res = ServiceResponse::new(req, HttpResponse::Ok().force_close().finish()); for unit in &mut format.0 { - unit.render_response(&resp); + unit.render_response(&res); } let entry_time = OffsetDateTime::now_utc(); @@ -784,9 +890,10 @@ mod tests { unit.render_request(now, &req); } - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let req = TestRequest::default().to_http_request(); + let res = ServiceResponse::new(req, HttpResponse::Ok().force_close().finish()); for unit in &mut format.0 { - unit.render_response(&resp); + unit.render_response(&res); } let render = |fmt: &mut fmt::Formatter<'_>| { @@ -815,9 +922,10 @@ mod tests { unit.render_request(now, &req); } - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let req = TestRequest::default().to_http_request(); + let res = ServiceResponse::new(req, HttpResponse::Ok().finish()); for unit in &mut format.0 { - unit.render_response(&resp); + unit.render_response(&res); } let entry_time = OffsetDateTime::now_utc(); @@ -832,7 +940,7 @@ mod tests { } #[actix_rt::test] - async fn test_custom_closure_log() { + async fn test_custom_closure_req_log() { let mut logger = Logger::new("test %{CUSTOM}xi") .custom_request_replace("CUSTOM", |_req: &ServiceRequest| -> String { String::from("custom_log") @@ -857,6 +965,38 @@ mod tests { assert_eq!(log_output, "custom_log"); } + #[actix_rt::test] + async fn test_custom_closure_response_log() { + let mut logger = Logger::new("test %{CUSTOM}xo").custom_response_replace( + "CUSTOM", + |res: &ServiceResponse| -> String { + if res.status().as_u16() == 200 { + String::from("custom_log") + } else { + String::from("-") + } + }, + ); + let mut unit = Rc::get_mut(&mut logger.0).unwrap().format.0[1].clone(); + + let label = match &unit { + FormatText::CustomResponse(label, _) => label, + ft => panic!("expected CustomResponse, found {:?}", ft), + }; + + assert_eq!(label, "CUSTOM"); + + let req = TestRequest::default().to_http_request(); + let resp_ok = ServiceResponse::new(req, HttpResponse::Ok().finish()); + let now = OffsetDateTime::now_utc(); + unit.render_response(&resp_ok); + + let render = |fmt: &mut fmt::Formatter<'_>| unit.render(fmt, 1024, now); + + let log_output = FormatDisplay(&render).to_string(); + assert_eq!(log_output, "custom_log"); + } + #[actix_rt::test] async fn test_closure_logger_in_middleware() { let captured = "custom log replacement"; From 73b94e902d8fd7c6936728d00e1229b321c69327 Mon Sep 17 00:00:00 2001 From: kkocdko <31189892+kkocdko@users.noreply.github.com> Date: Sun, 9 Oct 2022 19:44:10 +0800 Subject: [PATCH 522/861] fix xhtml pages' `content-disposition` (#2903) Co-authored-by: Yuki Okushi --- actix-files/CHANGES.md | 2 ++ actix-files/src/named.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index a71bf14fa..6e57bf7a7 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx +- XHTML files now use `Content-Disposition: inline` instead of `attachment`. [#2903] - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. +[#2903]: https://github.com/actix/actix-web/pull/2903 ## 0.6.2 - 2022-07-23 - Allow partial range responses for video content to start streaming sooner. [#2817] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 1213534c2..23d3093dc 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -132,7 +132,7 @@ impl NamedFile { mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline, mime::APPLICATION => match ct.subtype() { mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, - name if name == "wasm" => DispositionType::Inline, + name if name == "wasm" || name == "xhtml" => DispositionType::Inline, _ => DispositionType::Attachment, }, _ => DispositionType::Attachment, From f8cb71e789b7248ea0e61c6af27fc7dc79ddaeb2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 14 Oct 2022 13:20:38 +0200 Subject: [PATCH 523/861] remove incomplete doc comment --- actix-web/src/service.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 0ad92d8a1..ea23f09f5 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -327,9 +327,7 @@ impl ServiceRequest { .push(extensions); } - /// Creates a context object for use with a [guard](crate::guard). - /// - /// Useful if you are implementing + /// Creates a context object for use with a routing [guard](crate::guard). #[inline] pub fn guard_ctx(&self) -> GuardContext<'_> { GuardContext { req: self } From 068909f1b3a24c4a38e485e265f976f5d7f0237c Mon Sep 17 00:00:00 2001 From: Alex <100831787+crudiedo@users.noreply.github.com> Date: Fri, 14 Oct 2022 17:52:13 +0600 Subject: [PATCH 524/861] Replace deprecated twoway with memchr (#2909) --- actix-multipart/Cargo.toml | 2 +- actix-multipart/src/server.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 32ea49a24..6f631fcf1 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -24,7 +24,7 @@ httparse = "1.3" local-waker = "0.1" log = "0.4" mime = "0.3" -twoway = "0.2" +memchr = "2.5" [dev-dependencies] actix-rt = "2.2" diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c3757177f..1d0510039 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -606,7 +606,7 @@ impl InnerField { } loop { - return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") { + return if let Some(idx) = memchr::memmem::find(&payload.buf[pos..], b"\r") { let cur = pos + idx; // check if we have enough data for boundary detection @@ -827,7 +827,7 @@ impl PayloadBuffer { /// Read until specified ending fn read_until(&mut self, line: &[u8]) -> Result, MultipartError> { - let res = twoway::find_bytes(&self.buf, line) + let res = memchr::memmem::find(&self.buf, line) .map(|idx| self.buf.split_to(idx + line.len()).freeze()); if res.is_none() && self.eof { From 83cd061c862e82931895a8003fef5c191db0e5dd Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 25 Oct 2022 23:37:04 +0800 Subject: [PATCH 525/861] remove fakeshadow from author lists (#2921) --- actix-files/Cargo.toml | 1 - awc/Cargo.toml | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 33de0e6d9..018acdfb1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -3,7 +3,6 @@ name = "actix-files" version = "0.6.2" authors = [ "Nikolay Kim ", - "fakeshadow <24548779@qq.com>", "Rob Ede ", ] description = "Static file serving for Actix Web" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2f0027725..e7ac43d22 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "awc" version = "3.0.1" -authors = [ - "Nikolay Kim ", - "fakeshadow <24548779@qq.com>", -] +authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library" keywords = ["actix", "http", "framework", "async", "web"] categories = [ From a2e2c30d59a2b04880c03953e3d2bb7c7ba48b10 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 30 Oct 2022 19:47:49 +0000 Subject: [PATCH 526/861] use tokio-util deps directly where possible --- .github/workflows/upload-doc.yml | 2 +- actix-http/Cargo.toml | 2 ++ actix-http/examples/ws.rs | 2 +- actix-http/src/h1/client.rs | 2 +- actix-http/src/h1/codec.rs | 2 +- actix-http/src/h1/dispatcher.rs | 6 ++++-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/dispatcher.rs | 4 +++- actix-web-actors/Cargo.toml | 1 + actix-web-actors/src/ws.rs | 2 +- actix-web/src/http/header/accept.rs | 10 +++++----- 11 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 07f839e34..c47ea1d70 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -28,7 +28,7 @@ jobs: run: echo '' > target/doc/index.html - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4.4.0 + uses: JamesIves/github-pages-deploy-action@v4.4.1 with: folder: target/doc single-commit: true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 30e436160..a8b888ef4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -77,6 +77,8 @@ mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +tokio = { version = "1.13.1", features = [] } +tokio-util = { version = "0.7", features = ["io", "codec"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index c4f0503cd..6af6d5095 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -10,13 +10,13 @@ use std::{ time::Duration, }; -use actix_codec::Encoder; use actix_http::{body::BodyStream, error::Error, ws, HttpService, Request, Response}; use actix_rt::time::{interval, Interval}; use actix_server::Server; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::{ready, Stream}; +use tokio_util::codec::Encoder; use tracing::{info, trace}; #[actix_rt::main] diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 75c88d00c..6a0d531d0 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -1,9 +1,9 @@ use std::{fmt, io}; -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use http::{Method, Version}; +use tokio_util::codec::{Decoder, Encoder}; use super::{ decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 80afd7455..e11f175c9 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -1,9 +1,9 @@ use std::{fmt, io}; -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::BytesMut; use http::{Method, Version}; +use tokio_util::codec::{Decoder, Encoder}; use super::{ decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 81090667d..60660b85b 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,13 +8,15 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Decoder as _, Encoder as _, Framed, FramedParts}; +use actix_codec::{Framed, FramedParts}; use actix_rt::time::sleep_until; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_util::codec::{Decoder as _, Encoder as _}; use tracing::{error, trace}; use crate::{ @@ -1004,7 +1006,7 @@ where this.read_buf.reserve(HW_BUFFER_SIZE - remaining); } - match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { + match tokio_util::io::poll_read_buf(io.as_mut(), cx, this.read_buf) { Poll::Ready(Ok(n)) => { this.flags.remove(Flags::FINISHED); diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 4a2e741b6..6a149f9a4 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -1,7 +1,7 @@ -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; +use tokio_util::codec::{Decoder, Encoder}; use tracing::error; use super::{ diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 2f6b2363b..396f1e86c 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -76,7 +76,9 @@ mod inner { use pin_project_lite::pin_project; use tracing::debug; - use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; + use actix_codec::Framed; + use tokio::io::{AsyncRead, AsyncWrite}; + use tokio_util::codec::{Decoder, Encoder}; use crate::{body::BoxBody, Response}; diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 8222fc864..26b1c09de 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -24,6 +24,7 @@ bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" tokio = { version = "1.13.1", features = ["sync"] } +tokio-util = { version = "0.7", features = ["codec"] } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 9a4880159..e1110eddb 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -74,7 +74,6 @@ use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, SpawnHandle, }; -use actix_codec::{Decoder as _, Encoder as _}; use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, @@ -92,6 +91,7 @@ use bytestring::ByteString; use futures_core::Stream; use pin_project_lite::pin_project; use tokio::sync::oneshot; +use tokio_util::codec::{Decoder as _, Encoder as _}; /// Builder for Websocket session response. /// diff --git a/actix-web/src/http/header/accept.rs b/actix-web/src/http/header/accept.rs index 744c9b6e8..1be136b19 100644 --- a/actix-web/src/http/header/accept.rs +++ b/actix-web/src/http/header/accept.rs @@ -6,8 +6,7 @@ use super::{common_header, QualityItem}; use crate::http::header; common_header! { - /// `Accept` header, defined - /// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) + /// `Accept` header, defined in [RFC 7231 §5.3.2]. /// /// The `Accept` header field can be used by user agents to specify /// response media types that are acceptable. Accept header fields can @@ -71,6 +70,8 @@ common_header! { /// ]) /// ); /// ``` + /// + /// [RFC 7231 §5.3.2]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 (Accept, header::ACCEPT) => (QualityItem)* test_parse_and_format { @@ -101,13 +102,12 @@ common_header! { vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ QualityItem::max(mime::TEXT_PLAIN_UTF_8), - ]))); + ]))); crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ - QualityItem::new(mime::TEXT_PLAIN_UTF_8, - q(0.5)), + QualityItem::new(mime::TEXT_PLAIN_UTF_8, q(0.5)), ]))); #[test] From 45b77c68195eb933231290a09e9f6a0cca56aad8 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 4 Nov 2022 02:42:22 +0200 Subject: [PATCH 527/861] GitHub Workflows security hardening (#2923) --- .github/workflows/bench.yml | 3 +++ .github/workflows/ci-post-merge.yml | 3 +++ .github/workflows/ci.yml | 3 +++ .github/workflows/upload-doc.yml | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a4b54ca7a..008c33f89 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -5,6 +5,9 @@ on: branches: - master +permissions: + contents: read # to fetch code (actions/checkout) + jobs: check_benchmark: runs-on: ubuntu-latest diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 1ee97b591..6d76301d8 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -4,6 +4,9 @@ on: push: branches: [master] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build_and_test_nightly: strategy: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de1e1fe18..07e21ef43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: push: branches: [master] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build_and_test: strategy: diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index c47ea1d70..ac181b3f9 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -4,8 +4,12 @@ on: push: branches: [master] +permissions: {} jobs: build: + permissions: + contents: write # to push changes in repo (jamesives/github-pages-deploy-action) + runs-on: ubuntu-latest steps: From 10650435281bd98027b3b60a2b26bb8a243f0b02 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:00:59 +0000 Subject: [PATCH 528/861] ci: use dtolnay's rust-toolchain action --- .github/workflows/ci-post-merge.yml | 33 ++++++------------------ .github/workflows/ci.yml | 23 ++++------------- .github/workflows/clippy-fmt.yml | 39 ++++++++--------------------- .github/workflows/upload-doc.yml | 14 +++-------- 4 files changed, 28 insertions(+), 81 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 6d76301d8..7ac6388d4 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -95,29 +95,21 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Install cargo-hack uses: taiki-e/install-action@cargo-hack - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset } - + run: cargo ci-check-all-feature-powerset + - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset-linux } + run: cargo ci-check-all-feature-powerset-linux nextest: name: nextest @@ -130,24 +122,15 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Install nextest uses: taiki-e/install-action@nextest - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - name: Test with cargo-nextest - uses: actions-rs/cargo@v1 - with: - command: nextest - args: run + run: cargo nextest run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07e21ef43..b45f5469f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,16 +99,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 @@ -126,20 +120,13 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust (nightly) - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@nightly - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - name: doc tests - uses: actions-rs/cargo@v1 + run: cargo ci-doctest timeout-minutes: 60 - with: { command: ci-doctest } diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index bc2cec145..1587a0b1b 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -9,54 +9,37 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rustfmt - - name: Check with rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - uses: dtolnay/rust-toolchain@nightly + - run: cargo fmt --all -- --check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: clippy - override: true + - uses: dtolnay/rust-toolchain@stable + with: { components: clippy } - name: Generate Cargo.lock uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - + - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --tests --examples --all-features + token: ${{ secrets.GITHUB_TOKEN }} lint-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rust-docs + + - uses: dtolnay/rust-toolchain@stable + with: { components: rust-docs } + - name: Check for broken intra-doc links uses: actions-rs/cargo@v1 env: diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index ac181b3f9..9aadafafc 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -15,18 +15,12 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@nightly - name: Build Docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --workspace --all-features --no-deps + run: cargo +nightly doc --no-deps --workspace --all-features + env: + RUSTDOCFLAGS: --cfg=docsrs - name: Tweak HTML run: echo '' > target/doc/index.html From fcd06c9896c3ec53032be52ed94e187b30c99bc7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:28:06 +0000 Subject: [PATCH 529/861] workaround zstd msrv issue --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b45f5469f..f1c839e8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,7 @@ jobs: cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc + cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 - name: Generate Cargo.lock uses: actions-rs/cargo@v1 From d97bd7ec17d0dfd67d43f96abf8d8cfd5239e895 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:37:23 +0000 Subject: [PATCH 530/861] fix msrv CI --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1c839e8e..421becc63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,6 @@ jobs: cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc - cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 - name: Generate Cargo.lock uses: actions-rs/cargo@v1 @@ -67,6 +66,11 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 + - name: workaround MSRV issues + if: matrix.version != 'stable' + run: | + cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 + - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } From d708a4de6d0c7efb54520f48d0a3efaa28db19da Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:04:24 +0000 Subject: [PATCH 531/861] add acceptable guard (#2265) --- actix-web/CHANGES.md | 2 + actix-web/src/guard/acceptable.rs | 99 ++++++++++++++++++++++++ actix-web/src/{guard.rs => guard/mod.rs} | 3 + 3 files changed, 104 insertions(+) create mode 100644 actix-web/src/guard/acceptable.rs rename actix-web/src/{guard.rs => guard/mod.rs} (99%) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index a018bc248..06a2cccc9 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,7 +5,9 @@ - Add `ContentDisposition::attachment` constructor. [#2867] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] - Add `Logger::custom_response_replace()`. [#2631] +- Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +[#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 diff --git a/actix-web/src/guard/acceptable.rs b/actix-web/src/guard/acceptable.rs new file mode 100644 index 000000000..a31494a18 --- /dev/null +++ b/actix-web/src/guard/acceptable.rs @@ -0,0 +1,99 @@ +use super::{Guard, GuardContext}; +use crate::http::header::Accept; + +/// A guard that verifies that an `Accept` header is present and it contains a compatible MIME type. +/// +/// An exception is that matching `*/*` must be explicitly enabled because most browsers send this +/// as part of their `Accept` header for almost every request. +/// +/// # Examples +/// ``` +/// use actix_web::{guard::Acceptable, web, HttpResponse}; +/// +/// web::resource("/images") +/// .guard(Acceptable::new(mime::IMAGE_STAR)) +/// .default_service(web::to(|| async { +/// HttpResponse::Ok().body("only called when images responses are acceptable") +/// })); +/// ``` +#[derive(Debug, Clone)] +pub struct Acceptable { + mime: mime::Mime, + + /// Wether to match `*/*` mime type. + /// + /// Defaults to false because it's not very useful otherwise. + match_star_star: bool, +} + +impl Acceptable { + /// Constructs new `Acceptable` guard with the given `mime` type/pattern. + pub fn new(mime: mime::Mime) -> Self { + Self { + mime, + match_star_star: false, + } + } + + /// Allows `*/*` in the `Accept` header to pass the guard check. + pub fn match_star_star(mut self) -> Self { + self.match_star_star = true; + self + } +} + +impl Guard for Acceptable { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + let accept = match ctx.header::() { + Some(hdr) => hdr, + None => return false, + }; + + let target_type = self.mime.type_(); + let target_subtype = self.mime.subtype(); + + for mime in accept.0.into_iter().map(|q| q.item) { + return match (mime.type_(), mime.subtype()) { + (typ, subtype) if typ == target_type && subtype == target_subtype => true, + (typ, mime::STAR) if typ == target_type => true, + (mime::STAR, mime::STAR) if self.match_star_star => true, + _ => continue, + }; + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{http::header, test::TestRequest}; + + #[test] + fn test_acceptable() { + let req = TestRequest::default().to_srv_request(); + assert!(!Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + + let req = TestRequest::default() + .insert_header((header::ACCEPT, "application/json")) + .to_srv_request(); + assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + + let req = TestRequest::default() + .insert_header((header::ACCEPT, "text/html, application/json")) + .to_srv_request(); + assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + } + + #[test] + fn test_acceptable_star() { + let req = TestRequest::default() + .insert_header((header::ACCEPT, "text/html, */*;q=0.8")) + .to_srv_request(); + + assert!(Acceptable::new(mime::APPLICATION_JSON) + .match_star_star() + .check(&req.guard_ctx())); + } +} diff --git a/actix-web/src/guard.rs b/actix-web/src/guard/mod.rs similarity index 99% rename from actix-web/src/guard.rs rename to actix-web/src/guard/mod.rs index ef1301075..5fcaec0de 100644 --- a/actix-web/src/guard.rs +++ b/actix-web/src/guard/mod.rs @@ -56,6 +56,9 @@ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _}; +mod acceptable; +pub use self::acceptable::Acceptable; + /// Provides access to request parts that are useful during routing. #[derive(Debug)] pub struct GuardContext<'a> { From e7c34f2e45125d00c899994473212703731fd605 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:38:36 +0000 Subject: [PATCH 532/861] tweak form docs --- actix-web/src/types/form.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/actix-web/src/types/form.rs b/actix-web/src/types/form.rs index 9c09c6b73..d73f8ba74 100644 --- a/actix-web/src/types/form.rs +++ b/actix-web/src/types/form.rs @@ -35,6 +35,7 @@ use crate::{ /// /// Use [`FormConfig`] to configure extraction options. /// +/// ## Examples /// ``` /// use actix_web::{post, web}; /// use serde::Deserialize; @@ -46,20 +47,18 @@ use crate::{ /// /// // This handler is only called if: /// // - request headers declare the content type as `application/x-www-form-urlencoded` -/// // - request payload is deserialized into a `Info` struct from the URL encoded format +/// // - request payload deserializes into an `Info` struct from the URL encoded format /// #[post("/")] -/// async fn index(form: web::Form) -> String { +/// async fn index(web::Form(form): web::Form) -> String { /// format!("Welcome {}!", form.name) /// } /// ``` /// /// # Responder -/// The `Form` type also allows you to create URL encoded responses: -/// simply return a value of type Form where T is the type to be URL encoded. -/// The type must implement [`serde::Serialize`]. -/// -/// Responses use +/// The `Form` type also allows you to create URL encoded responses by returning a value of type +/// `Form` where `T` is the type to be URL encoded, as long as `T` implements [`Serialize`]. /// +/// ## Examples /// ``` /// use actix_web::{get, web}; /// use serde::Serialize; @@ -77,7 +76,7 @@ use crate::{ /// #[get("/")] /// async fn index() -> web::Form { /// web::Form(SomeForm { -/// name: "actix".into(), +/// name: "actix".to_owned(), /// age: 123 /// }) /// } From 3c69d078b2ad35672394f00d6d8acf1985a63bb8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:44:52 +0000 Subject: [PATCH 533/861] add redirect service (#1961) --- .github/workflows/clippy-fmt.yml | 2 +- actix-web/CHANGES.md | 2 + actix-web/src/lib.rs | 1 + actix-web/src/redirect.rs | 238 +++++++++++++++++++++++++++++++ actix-web/src/web.rs | 30 +++- 5 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 actix-web/src/redirect.rs diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 1587a0b1b..e94c4d1af 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -10,6 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly + with: { components: rustfmt } - run: cargo fmt --all -- --check clippy: @@ -21,7 +22,6 @@ jobs: with: { components: clippy } - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 06a2cccc9..6440ad693 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,8 +5,10 @@ - Add `ContentDisposition::attachment` constructor. [#2867] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] - Add `Logger::custom_response_replace()`. [#2631] +- Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +[#1961]: https://github.com/actix/actix-web/pull/1961 [#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 8d9e2dbcd..338541208 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -86,6 +86,7 @@ mod helpers; pub mod http; mod info; pub mod middleware; +mod redirect; mod request; mod request_data; mod resource; diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs new file mode 100644 index 000000000..ca9e23aa4 --- /dev/null +++ b/actix-web/src/redirect.rs @@ -0,0 +1,238 @@ +//! See [`Redirect`] for service/responder documentation. + +use std::borrow::Cow; + +use actix_utils::future::ready; + +use crate::{ + dev::{fn_service, AppService, HttpServiceFactory, ResourceDef, ServiceRequest}, + http::{header::LOCATION, StatusCode}, + HttpRequest, HttpResponse, Responder, +}; + +/// An HTTP service for redirecting one path to another path or URL. +/// +/// By default, the "307 Temporary Redirect" status is used when responding. See [this MDN +/// article][mdn-redirects] on why 307 is preferred over 302. +/// +/// # Examples +/// As service: +/// ``` +/// use actix_web::{web, App}; +/// +/// App::new() +/// // redirect "/duck" to DuckDuckGo +/// .service(web::redirect("/duck", "https://duck.com")) +/// .service( +/// // redirect "/api/old" to "/api/new" +/// web::scope("/api").service(web::redirect("/old", "/new")) +/// ); +/// ``` +/// +/// As responder: +/// ``` +/// use actix_web::web::Redirect; +/// +/// async fn handler() -> impl Responder { +/// // sends a permanent (308) redirect to duck.com +/// Redirect::to("https://duck.com").permanent() +/// } +/// # actix_web::web::to(handler); +/// ``` +/// +/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections +#[derive(Debug, Clone)] +pub struct Redirect { + from: Cow<'static, str>, + to: Cow<'static, str>, + status_code: StatusCode, +} + +impl Redirect { + /// Construct a new `Redirect` service that matches a path. + /// + /// This service will match exact paths equal to `from` within the current scope. I.e., when + /// registered on the root `App`, it will match exact, whole paths. But when registered on a + /// `Scope`, it will match paths under that scope, ignoring the defined scope prefix, just like + /// a normal `Resource` or `Route`. + /// + /// The `to` argument can be path or URL; whatever is provided shall be used verbatim when + /// setting the redirect location. This means that relative paths can be used to navigate + /// relatively to matched paths. + /// + /// Prefer [`Redirect::to()`](Self::to) when using `Redirect` as a responder since `from` has + /// no meaning in that context. + /// + /// # Examples + /// ``` + /// # use actix_web::{web::Redirect, App}; + /// App::new() + /// // redirects "/oh/hi/mark" to "/oh/bye/johnny" + /// .service(Redirect::new("/oh/hi/mark", "../../bye/johnny")); + /// ``` + pub fn new(from: impl Into>, to: impl Into>) -> Self { + Self { + from: from.into(), + to: to.into(), + status_code: StatusCode::TEMPORARY_REDIRECT, + } + } + + /// Construct a new `Redirect` to use as a responder. + /// + /// Only receives the `to` argument since responders do not need to do route matching. + /// + /// # Examples + /// ``` + /// use actix_web::web::Redirect; + /// + /// async fn admin_page() -> impl Responder { + /// // sends a temporary 307 redirect to the login path + /// Redirect::to("/login") + /// } + /// # actix_web::web::to(handler); + /// ``` + pub fn to(to: impl Into>) -> Self { + Self { + from: "/".into(), + to: to.into(), + status_code: StatusCode::TEMPORARY_REDIRECT, + } + } + + /// Use the "308 Permanent Redirect" status when responding. + /// + /// See [this MDN article][mdn-redirects] on why 308 is preferred over 301. + /// + /// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections + pub fn permanent(self) -> Self { + self.using_status_code(StatusCode::PERMANENT_REDIRECT) + } + + /// Use the "307 Temporary Redirect" status when responding. + /// + /// See [this MDN article][mdn-redirects] on why 307 is preferred over 302. + /// + /// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections + pub fn temporary(self) -> Self { + self.using_status_code(StatusCode::TEMPORARY_REDIRECT) + } + + /// Use the "303 See Other" status when responding. + /// + /// This status code is semantically correct as the response to a successful login, for example. + pub fn see_other(self) -> Self { + self.using_status_code(StatusCode::SEE_OTHER) + } + + /// Allows the use of custom status codes for less common redirect types. + /// + /// In most cases, the default status ("308 Permanent Redirect") or using the `temporary` + /// method, which uses the "307 Temporary Redirect" status have more consistent behavior than + /// 301 and 302 codes, respectively. + /// + /// ``` + /// # use actix_web::{http::StatusCode, web::Redirect}; + /// // redirects would use "301 Moved Permanently" status code + /// Redirect::new("/old", "/new") + /// .using_status_code(StatusCode::MOVED_PERMANENTLY); + /// + /// // redirects would use "302 Found" status code + /// Redirect::new("/old", "/new") + /// .using_status_code(StatusCode::FOUND); + /// ``` + pub fn using_status_code(mut self, status: StatusCode) -> Self { + self.status_code = status; + self + } +} + +impl HttpServiceFactory for Redirect { + fn register(self, config: &mut AppService) { + let redirect = self.clone(); + let rdef = ResourceDef::new(self.from.into_owned()); + let redirect_factory = fn_service(move |mut req: ServiceRequest| { + let res = redirect.clone().respond_to(req.parts_mut().0); + ready(Ok(req.into_response(res.map_into_boxed_body()))) + }); + + config.register_service(rdef, None, redirect_factory, None) + } +} + +impl Responder for Redirect { + type Body = (); + + fn respond_to(self, _req: &HttpRequest) -> HttpResponse { + let mut res = HttpResponse::with_body(self.status_code, ()); + + if let Ok(hdr_val) = self.to.parse() { + res.headers_mut().insert(LOCATION, hdr_val); + } else { + log::error!( + "redirect target location can not be converted to header value: {:?}", + self.to + ); + } + + res + } +} + +#[cfg(test)] +mod tests { + use crate::{dev::Service, http::StatusCode, test, App}; + + use super::*; + + #[actix_rt::test] + async fn absolute_redirects() { + let redirector = Redirect::new("/one", "/two").permanent(); + + let svc = test::init_service(App::new().service(redirector)).await; + + let req = test::TestRequest::default().uri("/one").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(308).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "/two"); + } + + #[actix_rt::test] + async fn relative_redirects() { + let redirector = Redirect::new("/one", "two").permanent(); + + let svc = test::init_service(App::new().service(redirector)).await; + + let req = test::TestRequest::default().uri("/one").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(308).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "two"); + } + + #[actix_rt::test] + async fn temporary_redirects() { + let external_service = Redirect::new("/external", "https://duck.com"); + + let svc = test::init_service(App::new().service(external_service)).await; + + let req = test::TestRequest::default().uri("/external").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(307).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "https://duck.com"); + } + + #[actix_rt::test] + async fn as_responder() { + let responder = Redirect::to("https://duck.com"); + + let req = test::TestRequest::default().to_http_request(); + let res = responder.respond_to(&req); + + assert_eq!(res.status(), StatusCode::from_u16(307).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "https://duck.com"); + } +} diff --git a/actix-web/src/web.rs b/actix-web/src/web.rs index f5845d7f6..0533f7f8f 100644 --- a/actix-web/src/web.rs +++ b/actix-web/src/web.rs @@ -11,10 +11,12 @@ //! - [`Bytes`]: Raw payload //! //! # Responders -//! - [`Json`]: JSON request payload -//! - [`Bytes`]: Raw request payload +//! - [`Json`]: JSON response +//! - [`Form`]: URL-encoded response +//! - [`Bytes`]: Raw bytes response +//! - [`Redirect`](Redirect::to): Convenient redirect responses -use std::future::Future; +use std::{borrow::Cow, future::Future}; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -26,6 +28,7 @@ use crate::{ pub use crate::config::ServiceConfig; pub use crate::data::Data; +pub use crate::redirect::Redirect; pub use crate::request_data::ReqData; pub use crate::types::*; @@ -45,6 +48,7 @@ pub use crate::types::*; /// For instance, to route `GET`-requests on any route matching `/users/{userid}/{friend}` and store /// `userid` and `friend` in the exposed `Path` object: /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -74,6 +78,7 @@ pub fn resource(path: T) -> Resource { /// - `/{project_id}/path2` /// - `/{project_id}/path3` /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -183,6 +188,25 @@ pub fn service(path: T) -> WebService { WebService::new(path) } +/// Create a relative or absolute redirect. +/// +/// See [`Redirect`] docs for usage details. +/// +/// # Examples +/// ``` +/// use actix_web::{web, App}; +/// +/// let app = App::new() +/// // the client will resolve this redirect to /api/to-path +/// .service(web::redirect("/api/from-path", "to-path")); +/// ``` +pub fn redirect( + from: impl Into>, + to: impl Into>, +) -> Redirect { + Redirect::new(from, to) +} + /// Executes blocking function on a thread pool, returns future that resolves to result of the /// function execution. pub fn block(f: F) -> impl Future> From 6d48593a6016cd75f6e7f128ee2bb2e6a28fcc86 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 23:28:31 +0000 Subject: [PATCH 534/861] fix doc tests --- actix-web/src/guard/mod.rs | 4 ++++ actix-web/src/redirect.rs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 5fcaec0de..7cd846b66 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -196,6 +196,7 @@ impl AnyGuard { } impl Guard for AnyGuard { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { for guard in &self.guards { if guard.check(ctx) { @@ -247,12 +248,14 @@ impl AllGuard { } impl Guard for AllGuard { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { for guard in &self.guards { if !guard.check(ctx) { return false; } } + true } } @@ -271,6 +274,7 @@ impl Guard for AllGuard { pub struct Not(pub G); impl Guard for Not { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { !self.0.check(ctx) } diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs index ca9e23aa4..5611cc368 100644 --- a/actix-web/src/redirect.rs +++ b/actix-web/src/redirect.rs @@ -31,7 +31,7 @@ use crate::{ /// /// As responder: /// ``` -/// use actix_web::web::Redirect; +/// use actix_web::{web::Redirect, Responder}; /// /// async fn handler() -> impl Responder { /// // sends a permanent (308) redirect to duck.com @@ -84,13 +84,13 @@ impl Redirect { /// /// # Examples /// ``` - /// use actix_web::web::Redirect; + /// use actix_web::{web::Redirect, Responder}; /// /// async fn admin_page() -> impl Responder { /// // sends a temporary 307 redirect to the login path /// Redirect::to("/login") /// } - /// # actix_web::web::to(handler); + /// # actix_web::web::to(admin_page); /// ``` pub fn to(to: impl Into>) -> Self { Self { From ede645ee4e65ee6ce930dd4a796c10d37fc1c4ce Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:11:04 +0000 Subject: [PATCH 535/861] bump criterion to 0.4 --- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a8b888ef4..313764d35 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -105,7 +105,7 @@ actix-tls = { version = "3", features = ["openssl"] } actix-web = "4" async-stream = "0.3" -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 19d6abc62..f3a5f15e4 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -27,7 +27,7 @@ serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index c7b54bd46..f60a35978 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -107,7 +107,7 @@ awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" const-str = "0.4" -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } From 17f7cd2aae1cf6cd5b21e3225ab3b3b95b128885 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:31:06 +0000 Subject: [PATCH 536/861] bump zstd to 0.12 --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 7 ++----- awc/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 313764d35..3ee285c81 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -96,7 +96,7 @@ actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.11", optional = true } +zstd = { version = "0.12", optional = true } [dev-dependencies] actix-http-test = { version = "3", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index f60a35978..9ace68811 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -38,10 +38,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"] compress-zstd = ["actix-http/compress-zstd", "__compress"] # Routing and runtime proc macros -macros = [ - "actix-macros", - "actix-web-codegen", -] +macros = ["actix-macros", "actix-web-codegen"] # Cookies support cookies = ["cookie"] @@ -119,7 +116,7 @@ static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.11" +zstd = "0.12" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e7ac43d22..b40becfec 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -87,7 +87,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } -trust-dns-resolver = { version = "0.21", optional = true } +trust-dns-resolver = { version = "0.22", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } @@ -107,7 +107,7 @@ static_assertions = "1.1" rcgen = "0.9" rustls-pemfile = "1" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.11" +zstd = "0.12" [[example]] name = "client" From 29bd6a1dd57705fc44a40d63327086643de1d2ad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:34:48 +0000 Subject: [PATCH 537/861] fix version requirement for futures_util --- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 4 ++-- actix-http/src/h1/dispatcher_tests.rs | 2 +- actix-http/tests/test_openssl.rs | 2 +- actix-http/tests/test_server.rs | 5 +---- actix-multipart/Cargo.toml | 4 ++-- actix-multipart/src/extractor.rs | 2 +- actix-multipart/src/server.rs | 2 +- actix-test/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 4 ++-- actix-web-actors/tests/test_ws.rs | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/Cargo.toml | 6 +++--- actix-web/src/app.rs | 2 +- actix-web/src/middleware/condition.rs | 2 +- actix-web/src/middleware/err_handlers.rs | 2 +- actix-web/src/types/payload.rs | 2 +- actix-web/src/types/readlines.rs | 2 +- awc/Cargo.toml | 6 +++--- awc/src/client/pool.rs | 2 +- awc/src/lib.rs | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 2 +- 24 files changed, 32 insertions(+), 35 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 018acdfb1..01dc2928a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -29,7 +29,7 @@ actix-web = { version = "4", default-features = false } bitflags = "1" bytes = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } http-range = "0.1.4" log = "0.4" mime = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0a9ddf947..86338fb06 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -39,7 +39,7 @@ awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" -futures-core = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } http = "0.2.5" log = "0.4" socket2 = "0.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3ee285c81..9f3977f0f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -67,7 +67,7 @@ bytes = "1" bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" @@ -107,7 +107,7 @@ actix-web = "4" async-stream = "0.3" criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } memchr = "2.4" once_cell = "1.9" rcgen = "0.9" diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 3eea859bf..d39c5bd69 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -64,7 +64,7 @@ fn drop_payload_service( fn echo_payload_service() -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { - use futures_util::stream::StreamExt as _; + use futures_util::StreamExt as _; let mut pl = req.take_payload(); let mut body = BytesMut::new(); diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index b97b2e45b..40dbb6ba4 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -16,7 +16,7 @@ use actix_utils::future::{err, ok, ready}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; use futures_core::Stream; -use futures_util::stream::{once, StreamExt as _}; +use futures_util::{stream::once, StreamExt as _}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e8d103c96..e70089b1d 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -15,10 +15,7 @@ use actix_service::fn_service; use actix_utils::future::{err, ok, ready}; use bytes::Bytes; use derive_more::{Display, Error}; -use futures_util::{ - stream::{once, StreamExt as _}, - FutureExt as _, -}; +use futures_util::{stream::once, FutureExt as _, StreamExt as _}; use regex::Regex; #[actix_rt::test] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6f631fcf1..3226850db 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -19,7 +19,7 @@ actix-web = { version = "4", default-features = false } bytes = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } httparse = "1.3" local-waker = "0.1" log = "0.4" @@ -29,6 +29,6 @@ memchr = "2.5" [dev-dependencies] actix-rt = "2.2" actix-http = "3" -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 1ad1f203d..d45c4869c 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -14,7 +14,7 @@ use crate::server::Multipart; /// ``` /// use actix_web::{web, HttpResponse, Error}; /// use actix_multipart::Multipart; -/// use futures_util::stream::StreamExt as _; +/// use futures_util::StreamExt as _; /// /// async fn index(mut payload: Multipart) -> Result { /// // iterate over multipart stream diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 1d0510039..9e0becd5c 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -868,7 +868,7 @@ mod tests { use actix_web::test::TestRequest; use actix_web::FromRequest; use bytes::Bytes; - use futures_util::{future::lazy, StreamExt}; + use futures_util::{future::lazy, StreamExt as _}; use std::time::Duration; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index eaea15d47..05d12c25a 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -37,8 +37,8 @@ actix-utils = "3" actix-web = { version = "4", default-features = false, features = ["cookies"] } awc = { version = "3", default-features = false, features = ["cookies"] } -futures-core = { version = "0.3.7", default-features = false, features = ["std"] } -futures-util = { version = "0.3.7", default-features = false, features = [] } +futures-core = { version = "0.3.17", default-features = false, features = ["std"] } +futures-util = { version = "0.3.17", default-features = false, features = [] } log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 26b1c09de..633c3c373 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -21,7 +21,7 @@ actix-web = { version = "4", default-features = false } bytes = "1" bytestring = "1" -futures-core = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } pin-project-lite = "0.2" tokio = { version = "1.13.1", features = ["sync"] } tokio-util = { version = "0.7", features = ["codec"] } @@ -35,4 +35,4 @@ actix-web = { version = "4", features = ["macros"] } mime = "0.3" env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.17", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a9eb37699..cf12a0052 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -3,7 +3,7 @@ use actix_http::ws::Codec; use actix_web::{web, App, HttpRequest}; use actix_web_actors::ws; use bytes::Bytes; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct Ws; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 2477364a6..da5577445 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -27,6 +27,6 @@ actix-test = "0.1" actix-utils = "3" actix-web = "4" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 9ace68811..6078d5739 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -79,8 +79,8 @@ cfg-if = "1" cookie = { version = "0.16", features = ["percent-encode"], optional = true } derive_more = "0.99.8" encoding_rs = "0.8" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } +futures-util = { version = "0.3.17", default-features = false } http = "0.2.8" itoa = "1" language-tags = "0.3" @@ -107,7 +107,7 @@ const-str = "0.4" criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" -futures-util = { version = "0.3.7", default-features = false, features = ["std"] } +futures-util = { version = "0.3.17", default-features = false, features = ["std"] } rand = "0.8" rcgen = "0.9" rustls-pemfile = "1" diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index ec37ff8a4..e53ab8080 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -5,7 +5,7 @@ use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; -use futures_util::future::FutureExt as _; +use futures_util::FutureExt as _; use crate::{ app_service::{AppEntry, AppInit, AppRoutingFactory}, diff --git a/actix-web/src/middleware/condition.rs b/actix-web/src/middleware/condition.rs index 65f25a67c..5e106c11f 100644 --- a/actix-web/src/middleware/condition.rs +++ b/actix-web/src/middleware/condition.rs @@ -7,7 +7,7 @@ use std::{ }; use futures_core::{future::LocalBoxFuture, ready}; -use futures_util::future::FutureExt as _; +use futures_util::FutureExt as _; use pin_project_lite::pin_project; use crate::{ diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs index 3a4e44a2c..4ddbc6318 100644 --- a/actix-web/src/middleware/err_handlers.rs +++ b/actix-web/src/middleware/err_handlers.rs @@ -351,7 +351,7 @@ mod tests { use actix_service::IntoService; use actix_utils::future::ok; use bytes::Bytes; - use futures_util::future::FutureExt as _; + use futures_util::FutureExt as _; use super::*; use crate::{ diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs index f17a4ed6d..4045cedb4 100644 --- a/actix-web/src/types/payload.rs +++ b/actix-web/src/types/payload.rs @@ -27,7 +27,7 @@ use crate::{ /// # Examples /// ``` /// use std::future::Future; -/// use futures_util::stream::StreamExt as _; +/// use futures_util::StreamExt as _; /// use actix_web::{post, web}; /// /// // `body: web::Payload` parameter extracts raw payload stream from request diff --git a/actix-web/src/types/readlines.rs b/actix-web/src/types/readlines.rs index 8a775a073..e75239968 100644 --- a/actix-web/src/types/readlines.rs +++ b/actix-web/src/types/readlines.rs @@ -177,7 +177,7 @@ where #[cfg(test)] mod tests { - use futures_util::stream::StreamExt as _; + use futures_util::StreamExt as _; use super::*; use crate::test::TestRequest; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b40becfec..cf64eed49 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -67,8 +67,8 @@ base64 = "0.13" bytes = "1" cfg-if = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" http = "0.2.5" itoa = "1" @@ -102,7 +102,7 @@ brotli = "3.3.3" const-str = "0.4" env_logger = "0.9" flate2 = "1.0.13" -futures-util = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.17", default-features = false } static_assertions = "1.1" rcgen = "0.9" rustls-pemfile = "1" diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs index 5655b5845..47c1fdd67 100644 --- a/awc/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -19,7 +19,7 @@ use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; -use futures_util::FutureExt; +use futures_util::FutureExt as _; use http::uri::Authority; use pin_project_lite::pin_project; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8d6ea759a..412ccbe61 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -83,7 +83,7 @@ //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { -//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! use futures_util::{SinkExt as _, StreamExt as _}; //! //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") diff --git a/awc/src/ws.rs b/awc/src/ws.rs index b316f68b4..4ef2e2b36 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -6,7 +6,7 @@ //! //! ```no_run //! use awc::{Client, ws}; -//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! use futures_util::{SinkExt as _, StreamExt as _}; //! //! #[actix_rt::main] //! async fn main() { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c4b468eeb..db987fdfa 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -139,7 +139,7 @@ async fn timeout_override() { #[actix_rt::test] async fn response_timeout() { - use futures_util::stream::{once, StreamExt as _}; + use futures_util::{stream::once, StreamExt as _}; let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { From 06c3513bc0da212d87c6fae5332e845146991027 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 21 Dec 2022 20:28:45 +0000 Subject: [PATCH 538/861] add Allow header to resource's default 405 handler (#2949) --- actix-web/CHANGES.md | 4 +++ actix-web/src/guard/mod.rs | 16 ++++++++++- actix-web/src/resource.rs | 59 +++++++++++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 6440ad693..8ea60266e 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -8,11 +8,15 @@ - Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +### Fixed +- Add `Allow` header to `Resource`'s default responses when no routes are matched. [#2949] + [#1961]: https://github.com/actix/actix-web/pull/1961 [#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 +[#2949]: https://github.com/actix/actix-web/pull/2949 ## 4.2.1 - 2022-09-12 diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 7cd846b66..e086f8648 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -286,11 +286,25 @@ pub fn Method(method: HttpMethod) -> impl Guard { MethodGuard(method) } +#[derive(Debug, Clone)] +pub(crate) struct RegisteredMethods(pub(crate) Vec); + /// HTTP method guard. -struct MethodGuard(HttpMethod); +#[derive(Debug)] +pub(crate) struct MethodGuard(HttpMethod); impl Guard for MethodGuard { fn check(&self, ctx: &GuardContext<'_>) -> bool { + let registered = ctx.req_data_mut().remove::(); + + if let Some(mut methods) = registered { + methods.0.push(self.0.clone()); + ctx.req_data_mut().insert(methods); + } else { + ctx.req_data_mut() + .insert(RegisteredMethods(vec![self.0.clone()])); + } + ctx.head().method == self.0 } } diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs index c5c6701e6..997036751 100644 --- a/actix-web/src/resource.rs +++ b/actix-web/src/resource.rs @@ -13,8 +13,9 @@ use crate::{ body::MessageBody, data::Data, dev::{ensure_leading_slash, AppService, ResourceDef}, - guard::Guard, + guard::{self, Guard}, handler::Handler, + http::header, route::{Route, RouteService}, service::{ BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, @@ -40,8 +41,11 @@ use crate::{ /// .route(web::get().to(|| HttpResponse::Ok()))); /// ``` /// -/// If no matching route could be found, *405* response code get returned. Default behavior could be -/// overridden with `default_resource()` method. +/// If no matching route is found, [a 405 response is returned with an appropriate Allow header][RFC +/// 9110 §15.5.6]. This default behavior can be overridden using +/// [`default_service()`](Self::default_service). +/// +/// [RFC 9110 §15.5.6]: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6 pub struct Resource { endpoint: T, rdef: Patterns, @@ -66,7 +70,19 @@ impl Resource { guards: Vec::new(), app_data: None, default: boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(HttpResponse::MethodNotAllowed())) + use crate::HttpMessage as _; + + let allowed = req.extensions().get::().cloned(); + + if let Some(methods) = allowed { + Ok(req.into_response( + HttpResponse::MethodNotAllowed() + .insert_header(header::Allow(methods.0)) + .finish(), + )) + } else { + Ok(req.into_response(HttpResponse::MethodNotAllowed())) + } })), } } @@ -309,13 +325,28 @@ where } } - /// Default service to be used if no matching route could be found. + /// Sets the default service to be used if no matching route is found. /// - /// You can use a [`Route`] as default service. + /// Unlike [`Scope`]s, a `Resource` does _not_ inherit its parent's default service. You can + /// use a [`Route`] as default service. /// - /// If a default service is not registered, an empty `405 Method Not Allowed` response will be - /// sent to the client instead. Unlike [`Scope`](crate::Scope)s, a [`Resource`] does **not** - /// inherit its parent's default service. + /// If a custom default service is not registered, an empty `405 Method Not Allowed` response + /// with an appropriate Allow header will be sent instead. + /// + /// # Examples + /// ``` + /// use actix_web::{App, HttpResponse, web}; + /// + /// let resource = web::resource("/test") + /// .route(web::get().to(HttpResponse::Ok)) + /// .default_service(web::to(|| { + /// HttpResponse::BadRequest() + /// })); + /// + /// App::new().service(resource); + /// ``` + /// + /// [`Scope`]: crate::Scope pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, @@ -606,7 +637,11 @@ mod tests { async fn test_default_resource() { let srv = init_service( App::new() - .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))) + .service( + web::resource("/test") + .route(web::get().to(HttpResponse::Ok)) + .route(web::delete().to(HttpResponse::Ok)), + ) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::BadRequest())) }), @@ -621,6 +656,10 @@ mod tests { .to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + assert_eq!( + resp.headers().get(header::ALLOW).unwrap().as_bytes(), + b"GET, DELETE" + ); let srv = init_service( App::new().service( From 6f0a6bd1bb7ffdd98fa5ce825b24a73c4d71d9a7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 1 Jan 2023 20:56:27 +0000 Subject: [PATCH 539/861] address clippy lints For intrepid commit message readers: The choice to add allows for the inlined format args lint instead of actually inlining them is not very clear because our actual real world MSRV is not clear. We currently claim 1.60 is our MSRV but this is mainly due to dependencies. I'm fairly sure that we could support < 1.58 if those deps are outdated in a users lockfile. We'll remove these allows again at some point soon. --- actix-files/src/lib.rs | 1 + actix-http-test/src/lib.rs | 3 +++ actix-http/benches/quality-value.rs | 2 ++ actix-http/src/body/sized_stream.rs | 2 +- actix-http/src/h1/chunked.rs | 2 +- actix-http/src/h1/encoder.rs | 2 +- actix-http/src/lib.rs | 3 ++- actix-http/tests/test_openssl.rs | 1 + actix-http/tests/test_rustls.rs | 1 + actix-http/tests/test_server.rs | 2 ++ actix-http/tests/test_ws.rs | 2 ++ actix-multipart/src/lib.rs | 2 +- actix-router/benches/quoter.rs | 2 ++ actix-router/src/lib.rs | 1 + actix-test/src/lib.rs | 2 ++ actix-web-actors/src/lib.rs | 1 + actix-web-codegen/src/route.rs | 2 +- actix-web/benches/server.rs | 2 ++ actix-web/examples/basic.rs | 2 ++ actix-web/examples/macroless.rs | 2 ++ actix-web/examples/on-connect.rs | 2 ++ actix-web/examples/uds.rs | 2 ++ actix-web/src/app.rs | 1 + actix-web/src/lib.rs | 1 + actix-web/tests/test_httpserver.rs | 2 ++ awc/examples/client.rs | 2 ++ awc/src/builder.rs | 2 +- awc/src/lib.rs | 3 ++- awc/src/request.rs | 4 +++- awc/src/ws.rs | 4 +++- awc/tests/test_client.rs | 2 ++ 31 files changed, 52 insertions(+), 10 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 40327e5e8..0fbe39a8e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -13,6 +13,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible, missing_docs, missing_debug_implementations)] +#![allow(clippy::uninlined_format_args)] use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 8636ef9c4..a66f7b486 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -2,6 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] @@ -87,6 +88,7 @@ pub async fn test_server_with_addr>( // notify TestServer that server and system have shut down // all thread managed resources should be dropped at this point + #[allow(clippy::let_underscore_future)] let _ = thread_stop_tx.send(()); }); @@ -294,6 +296,7 @@ impl Drop for TestServer { // without needing to await anything // signal server to stop + #[allow(clippy::let_underscore_future)] let _ = self.server.stop(true); // signal system to stop diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs index 33ba9c4c8..0ed274ded 100644 --- a/actix-http/benches/quality-value.rs +++ b/actix-http/benches/quality-value.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; const CODES: &[u16] = &[0, 1000, 201, 800, 550]; diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index e5e27b287..08cd81a0d 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -44,7 +44,7 @@ where #[inline] fn size(&self) -> BodySize { - BodySize::Sized(self.size as u64) + BodySize::Sized(self.size) } /// Attempts to pull out the next value of the underlying [`Stream`]. diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 4005ed892..fc9081b81 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -71,7 +71,7 @@ impl ChunkedState { match size.checked_mul(radix) { Some(n) => { - *size = n as u64; + *size = n; *size += rem as u64; Poll::Ready(Ok(ChunkedState::Size)) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 21cfd75c4..abe396ce2 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -450,7 +450,7 @@ impl TransferEncoding { buf.extend_from_slice(&msg[..len as usize]); - *remaining -= len as u64; + *remaining -= len; Ok(*remaining == 0) } else { Ok(true) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 864db4986..05f80eba4 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -21,7 +21,8 @@ #![allow( clippy::type_complexity, clippy::too_many_arguments, - clippy::borrow_interior_mutable_const + clippy::borrow_interior_mutable_const, + clippy::uninlined_format_args )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 40dbb6ba4..7464bee4e 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,4 +1,5 @@ #![cfg(feature = "openssl")] +#![allow(clippy::uninlined_format_args)] extern crate tls_openssl as openssl; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index d9ff42b7d..0b8197a69 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -1,4 +1,5 @@ #![cfg(feature = "rustls")] +#![allow(clippy::uninlined_format_args)] extern crate tls_rustls as rustls; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e70089b1d..0816ab221 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ convert::Infallible, io::{Read, Write}, diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 8b3ab8e1b..a9c1acd33 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ cell::Cell, convert::Infallible, diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 3d536e08d..37d03db49 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -2,7 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] -#![allow(clippy::borrow_interior_mutable_const)] +#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)] mod error; mod extractor; diff --git a/actix-router/benches/quoter.rs b/actix-router/benches/quoter.rs index c18f1620e..9ca06da39 100644 --- a/actix-router/benches/quoter.rs +++ b/actix-router/benches/quoter.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::borrow::Cow; diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 0febcf1ac..418dd432b 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -2,6 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 5efd9758e..1aff2dc83 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -321,6 +321,7 @@ where // all thread managed resources should be dropped at this point }); + #[allow(clippy::let_underscore_future)] let _ = thread_stop_tx.send(()); }); @@ -567,6 +568,7 @@ impl Drop for TestServer { // without needing to await anything // signal server to stop + #[allow(clippy::let_underscore_future)] let _ = self.server.stop(true); // signal system to stop diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 106bc5202..7a34048da 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -57,6 +57,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] mod context; pub mod ws; diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 7a0658468..e5493702d 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -155,7 +155,7 @@ impl Args { if !methods.insert(method) { return Err(syn::Error::new_spanned( &nv.lit, - &format!( + format!( "HTTP method defined more than once: `{}`", lit.value() ), diff --git a/actix-web/benches/server.rs b/actix-web/benches/server.rs index 0d45c9403..2c9f71dc5 100644 --- a/actix-web/benches/server.rs +++ b/actix-web/benches/server.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{web, App, HttpResponse}; use awc::Client; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/actix-web/examples/basic.rs b/actix-web/examples/basic.rs index 36b1cdd8f..60715f477 100644 --- a/actix-web/examples/basic.rs +++ b/actix-web/examples/basic.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer}; #[get("/resource1/{name}/index.html")] diff --git a/actix-web/examples/macroless.rs b/actix-web/examples/macroless.rs index 78ffd45c1..d3589da21 100644 --- a/actix-web/examples/macroless.rs +++ b/actix-web/examples/macroless.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; async fn index(req: HttpRequest) -> &'static str { diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs index 24c6f8418..57017fcd6 100644 --- a/actix-web/examples/on-connect.rs +++ b/actix-web/examples/on-connect.rs @@ -4,6 +4,8 @@ //! For an example of extracting a client TLS certificate, see: //! +#![allow(clippy::uninlined_format_args)] + use std::{any::Any, io, net::SocketAddr}; use actix_web::{ diff --git a/actix-web/examples/uds.rs b/actix-web/examples/uds.rs index cf0ffebde..ba4b25a29 100644 --- a/actix-web/examples/uds.rs +++ b/actix-web/examples/uds.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{get, web, HttpRequest}; #[cfg(unix)] use actix_web::{middleware, App, Error, HttpResponse, HttpServer}; diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index e53ab8080..353b82b19 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -712,6 +712,7 @@ mod tests { .route("/", web::to(|| async { "hello" })) } + #[allow(clippy::let_underscore_future)] let _ = init_service(my_app()); } } diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 338541208..6a94976c5 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -69,6 +69,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/actix-web/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs index 86e0575f3..861d76d93 100644 --- a/actix-web/tests/test_httpserver.rs +++ b/actix-web/tests/test_httpserver.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; diff --git a/awc/examples/client.rs b/awc/examples/client.rs index 16ad330b8..26edcfd62 100644 --- a/awc/examples/client.rs +++ b/awc/examples/client.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::error::Error as StdError; #[tokio::main] diff --git a/awc/src/builder.rs b/awc/src/builder.rs index c101d18f0..34a5f8505 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -210,7 +210,7 @@ where }; self.add_default_header(( header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), + format!("Basic {}", base64::encode(auth)), )) } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 412ccbe61..bb7f06c93 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,7 +105,8 @@ #![allow( clippy::type_complexity, clippy::borrow_interior_mutable_const, - clippy::needless_doctest_main + clippy::needless_doctest_main, + clippy::uninlined_format_args )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/awc/src/request.rs b/awc/src/request.rs index 102db3c16..331c80af7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -238,7 +238,7 @@ impl ClientRequest { self.insert_header(( header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), + format!("Basic {}", base64::encode(auth)), )) } @@ -565,6 +565,8 @@ mod tests { assert_eq!(req.head.version, Version::HTTP_2); let _ = req.headers_mut(); + + #[allow(clippy::let_underscore_future)] let _ = req.send_body(""); } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 4ef2e2b36..f905b8ef2 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -236,7 +236,7 @@ impl WebsocketsRequest { Some(password) => format!("{}:{}", username, password), None => format!("{}:", username), }; - self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth))) + self.header(AUTHORIZATION, format!("Basic {}", base64::encode(auth))) } /// Set HTTP bearer authentication header @@ -503,6 +503,8 @@ mod tests { .unwrap(), "Bearer someS3cr3tAutht0k3n" ); + + #[allow(clippy::let_underscore_future)] let _ = req.connect(); } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index db987fdfa..0949595cb 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ collections::HashMap, convert::Infallible, From 77459ec415a44ab90ed5b11ec6165f0dd0fe5e37 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 00:14:25 +0000 Subject: [PATCH 540/861] add h2c example --- actix-http/examples/h2c-detect.rs | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 actix-http/examples/h2c-detect.rs diff --git a/actix-http/examples/h2c-detect.rs b/actix-http/examples/h2c-detect.rs new file mode 100644 index 000000000..e1491324b --- /dev/null +++ b/actix-http/examples/h2c-detect.rs @@ -0,0 +1,54 @@ +//! An example that supports automatic selection of plaintext h1/h2c connections. +//! +//! Notably, both the following commands will work. +//! ```console +//! $ curl --http1.1 'http://localhost:8080/' +//! $ curl --http2-prior-knowledge 'http://localhost:8080/' +//! ``` + +use std::{convert::Infallible, io}; + +use actix_http::{HttpService, Protocol, Request, Response, StatusCode}; +use actix_rt::net::TcpStream; +use actix_server::Server; +use actix_service::{fn_service, ServiceFactoryExt}; + +const H2_PREFACE: &[u8] = b"PRI * HTTP/2"; + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("h2c-detect", ("127.0.0.1", 8080), || { + fn_service(move |io: TcpStream| async move { + let mut buf = [0; 12]; + + io.peek(&mut buf).await?; + + let proto = if buf == H2_PREFACE { + tracing::info!("selecting h2c"); + Protocol::Http2 + } else { + tracing::info!("selecting h1"); + Protocol::Http1 + }; + + let peer_addr = io.peer_addr().ok(); + Ok::<_, io::Error>((io, proto, peer_addr)) + }) + .and_then( + HttpService::build() + .finish(|_req: Request| async move { + Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) + }) + .map_err(|err| { + tracing::error!("{}", err); + io::Error::new(io::ErrorKind::Other, "http service dispatch error") + }), + ) + })? + .workers(2) + .run() + .await +} From d2364c80c4311d70f32a6a50e26a2a3ef43a9206 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 00:16:59 +0000 Subject: [PATCH 541/861] improve error handling on new new example --- actix-http/examples/h2c-detect.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/actix-http/examples/h2c-detect.rs b/actix-http/examples/h2c-detect.rs index e1491324b..550a03d2a 100644 --- a/actix-http/examples/h2c-detect.rs +++ b/actix-http/examples/h2c-detect.rs @@ -8,7 +8,7 @@ use std::{convert::Infallible, io}; -use actix_http::{HttpService, Protocol, Request, Response, StatusCode}; +use actix_http::{error::DispatchError, HttpService, Protocol, Request, Response, StatusCode}; use actix_rt::net::TcpStream; use actix_server::Server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -24,7 +24,7 @@ async fn main() -> io::Result<()> { fn_service(move |io: TcpStream| async move { let mut buf = [0; 12]; - io.peek(&mut buf).await?; + io.peek(&mut buf).await.map_err(DispatchError::Io)?; let proto = if buf == H2_PREFACE { tracing::info!("selecting h2c"); @@ -35,18 +35,11 @@ async fn main() -> io::Result<()> { }; let peer_addr = io.peer_addr().ok(); - Ok::<_, io::Error>((io, proto, peer_addr)) + Ok((io, proto, peer_addr)) }) - .and_then( - HttpService::build() - .finish(|_req: Request| async move { - Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) - }) - .map_err(|err| { - tracing::error!("{}", err); - io::Error::new(io::ErrorKind::Other, "http service dispatch error") - }), - ) + .and_then(HttpService::build().finish(|_req: Request| async move { + Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) + })) })? .workers(2) .run() From 7b936bc443d22fca679a1fca72b488adfecee3e6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 13:33:31 +0000 Subject: [PATCH 542/861] add some useful header name constants (#2956) --- actix-http/CHANGES.md | 8 +++++++ actix-http/src/header/common.rs | 39 +++++++++++++++++++++++++++++++++ actix-http/src/header/mod.rs | 35 +++++++++++++++++++---------- 3 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 actix-http/src/header/common.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 045ae461f..3c820ccf8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,12 +4,20 @@ ### Added - Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868] - Implement `MessageBody` for `Pin` where `B::Target: MessageBody`. [#2868] +- Header name constants in `header` module. [#2956] + - `CROSS_ORIGIN_EMBEDDER_POLICY` + - `CROSS_ORIGIN_OPENER_POLICY` + - `PERMISSIONS_POLICY` + - `X_FORWARDED_FOR` + - `X_FORWARDED_HOST` + - `X_FORWARDED_PROTO` ### Performance - Improve overall performance of operations on `Extensions`. [#2890] [#2868]: https://github.com/actix/actix-web/pull/2868 [#2890]: https://github.com/actix/actix-web/pull/2890 +[#2956]: https://github.com/actix/actix-web/pull/2956 ## 3.2.2 - 2022-09-11 diff --git a/actix-http/src/header/common.rs b/actix-http/src/header/common.rs new file mode 100644 index 000000000..52909099a --- /dev/null +++ b/actix-http/src/header/common.rs @@ -0,0 +1,39 @@ +//! Common header names not defined in [`http`]. +//! +//! Any headers added to this file will need to be re-exported from the list at `crate::headers`. + +use http::header::HeaderName; + +/// Response header that prevents a document from loading any cross-origin resources that don't +/// explicitly grant the document permission (using [CORP] or [CORS]). +/// +/// [CORP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP) +/// [CORS]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +pub const CROSS_ORIGIN_EMBEDDER_POLICY: HeaderName = + HeaderName::from_static("cross-origin-embedder-policy"); + +/// Response header that allows you to ensure a top-level document does not share a browsing context +/// group with cross-origin documents. +pub const CROSS_ORIGIN_OPENER_POLICY: HeaderName = + HeaderName::from_static("cross-origin-opener-policy"); + +/// Response header that conveys a desire that the browser blocks no-cors cross-origin/cross-site +/// requests to the given resource. +pub const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName = + HeaderName::from_static("cross-origin-resource-policy"); + +/// Response header that provides a mechanism to allow and deny the use of browser features in a +/// document or within any `

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) ![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1) +[![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From 392641658064469609edd366b6ad11c343ea0c88 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 12 Jun 2022 00:48:08 +0900 Subject: [PATCH 449/861] actix-http: Pull actix-web dev-dep from Git repo The published version of actix-web depends on a buggy version of zstd crate, temporarily use actix-web on git repo to avoid the build failure. Signed-off-by: Yuki Okushi --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2a4966884..6afffd002 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = "4" +actix-web = { git = "https://github.com/actix/actix-web", rev = "43671ae4aaf2c20150fd1e5ca54055eb5d114273" } async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } From 062127a210659a62bb6fc9827a0f9cab56b41752 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 12 Jun 2022 00:55:06 +0900 Subject: [PATCH 450/861] Revert "actix-http: Pull actix-web dev-dep from Git repo" This reverts commit 392641658064469609edd366b6ad11c343ea0c88. --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6afffd002..2a4966884 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } -actix-web = { git = "https://github.com/actix/actix-web", rev = "43671ae4aaf2c20150fd1e5ca54055eb5d114273" } +actix-web = "4" async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } From 265fa0d050ffe07306accaa25ceffca3a8a15dda Mon Sep 17 00:00:00 2001 From: Isabel Atkinson Date: Wed, 15 Jun 2022 17:38:10 -0400 Subject: [PATCH 451/861] Add link to MongoDB example in README (#2783) --- actix-web/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/actix-web/README.md b/actix-web/README.md index 65ee4efca..3fd0108ce 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -79,6 +79,7 @@ async fn main() -> std::io::Result<()> { - [Application State](https://github.com/actix/examples/tree/master/basics/state) - [JSON Handling](https://github.com/actix/examples/tree/master/json/json) - [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart) +- [MongoDB Integration](https://github.com/actix/examples/tree/master/databases/mongodb) - [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel) - [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite) - [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres) From 6b7196225e16f30c2bd99d5a4d8d3f25e040aac9 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 22 Jun 2022 20:08:06 +0900 Subject: [PATCH 452/861] Bump up MSRV to 1.57 (#2789) --- .github/workflows/ci.yml | 2 +- actix-files/CHANGES.md | 3 ++- actix-files/README.md | 2 +- actix-http-test/CHANGES.md | 4 ++-- actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 3 ++- actix-http/README.md | 2 +- actix-multipart/CHANGES.md | 4 ++-- actix-multipart/README.md | 2 +- actix-router/CHANGES.md | 4 ++-- actix-test/CHANGES.md | 4 ++-- actix-web-actors/CHANGES.md | 4 ++-- actix-web-actors/README.md | 2 +- actix-web-codegen/CHANGES.md | 3 ++- actix-web-codegen/README.md | 2 +- actix-web-codegen/tests/trybuild.rs | 2 +- .../tests/trybuild/route-duplicate-method-fail.stderr | 8 +++++--- .../tests/trybuild/route-missing-method-fail.stderr | 4 +++- .../tests/trybuild/route-unexpected-method-fail.stderr | 8 +++++--- actix-web/CHANGES.md | 3 ++- actix-web/README.md | 4 ++-- awc/CHANGES.md | 4 ++-- clippy.toml | 2 +- 23 files changed, 44 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95dc6ba99..49ad25ccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.56.0 # MSRV + - 1.57.0 # MSRV - stable name: ${{ matrix.target.name }} / ${{ matrix.version }} diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ff3ec13ac..b127cd9ea 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.6.1 - 2022-06-11 diff --git a/actix-files/README.md b/actix-files/README.md index 737c0edef..35db41c9a 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 3f0be5356..f91ef4081 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.0.0-beta.13 - 2022-02-16 diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 8b8cacc2e..9429bb760 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 980997a06..209f86cad 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.1.0 - 2022-06-11 diff --git a/actix-http/README.md b/actix-http/README.md index 388761aee..211f433e8 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 53fbf9393..ed5c97e1d 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.4.0 - 2022-02-25 diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 0b375bf8d..0b1e2df17 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 39ff98c39..1e4fc41f2 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.5.0 - 2022-02-22 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 9b84f04b0..43e306bb1 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 0.1.0-beta.13 - 2022-02-16 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index f143be29c..33d4712f8 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.1.0 - 2022-03-02 diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 39a10a4e2..8d64c0851 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 24ab212a8..a85d6c454 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,7 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.0.1 - 2022-06-11 diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index f02e8eb35..26f070f18 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) -![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 13eb84559..976b3d52c 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -1,4 +1,4 @@ -#[rustversion::stable(1.56)] // MSRV +#[rustversion::stable(1.57)] // MSRV #[test] fn compile_macros() { let t = trybuild::TestCases::new(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index 90cff1b1c..fe9274bc8 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -1,11 +1,13 @@ error: HTTP method defined more than once: `GET` - --> $DIR/route-duplicate-method-fail.rs:3:35 + --> tests/trybuild/route-duplicate-method-fail.rs:3:35 | 3 | #[route("/", method="GET", method="GET")] | ^^^^^ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> $DIR/route-duplicate-method-fail.rs:12:55 + --> tests/trybuild/route-duplicate-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index b1cefafde..284b2cf4a 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -10,4 +10,6 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpSer --> tests/trybuild/route-missing-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index dda366067..804ba69f3 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -1,11 +1,13 @@ error: Unexpected HTTP method: `UNEXPECTED` - --> $DIR/route-unexpected-method-fail.rs:3:21 + --> tests/trybuild/route-unexpected-method-fail.rs:3:21 | 3 | #[route("/", method="UNEXPECTED")] | ^^^^^^^^^^^^ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied - --> $DIR/route-unexpected-method-fail.rs:12:55 + --> tests/trybuild/route-unexpected-method-fail.rs:12:55 | 12 | let srv = actix_test::start(|| App::new().service(index)); - | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 307bba9ba..fb27cddfd 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,7 @@ # Changelog -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 4.1.0 - 2022-06-11 diff --git a/actix-web/README.md b/actix-web/README.md index 3fd0108ce..fdd4a8648 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) -![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) +![MSRV](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
@@ -33,7 +33,7 @@ - SSL support using OpenSSL or Rustls - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/) -- Runs on stable Rust 1.56+ +- Runs on stable Rust 1.57+ ## Documentation diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 622388286..e229a6d96 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,8 +1,8 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased - 2022-xx-xx ### Changed -- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ## 3.0.0 - 2022-03-07 diff --git a/clippy.toml b/clippy.toml index 62ca74234..5cccb362c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.56" +msrv = "1.57" From 5d0e8138eeab63647e1e36001e6ae8e2a8ac3722 Mon Sep 17 00:00:00 2001 From: e-rhodes <33500135+e-rhodes@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:02:03 -0600 Subject: [PATCH 453/861] Add getters for `&ServiceRequest` (#2786) --- actix-web/CHANGES.md | 3 +++ actix-web/src/service.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index fb27cddfd..3fa2f8f21 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,7 +2,10 @@ ## Unreleased - 2022-xx-xx - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +### Added +- Add `ServiceRequest::{parts, request}()` getter methods. [#2786] +[#2786]: https://github.com/actix/actix-web/pull/2786 ## 4.1.0 - 2022-06-11 ### Added diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index a9e809bf9..0ad92d8a1 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -95,6 +95,18 @@ impl ServiceRequest { (&mut self.req, &mut self.payload) } + /// Returns immutable accessors to inner parts. + #[inline] + pub fn parts(&self) -> (&HttpRequest, &Payload) { + (&self.req, &self.payload) + } + + /// Returns immutable accessor to inner [`HttpRequest`]. + #[inline] + pub fn request(&self) -> &HttpRequest { + &self.req + } + /// Derives a type from this request using an [extractor](crate::FromRequest). /// /// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy From de92b3be2e6894c353816c6bd29d2a4768ccb92f Mon Sep 17 00:00:00 2001 From: oatoam <31235342+oatoam@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:46:17 +0800 Subject: [PATCH 454/861] fix unrecoverable Err(Overflow) in websocket frame parser (#2790) --- actix-http/CHANGES.md | 6 +++++ actix-http/src/ws/frame.rs | 49 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 209f86cad..dd6051b85 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] + +### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2790]: https://github.com/actix/actix-web/pull/2790 + ## 3.1.0 - 2022-06-11 ### Changed diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 17e34e2ba..3659b6c3b 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -17,7 +17,6 @@ impl Parser { fn parse_metadata( src: &[u8], server: bool, - max_size: usize, ) -> Result)>, ProtocolError> { let chunk_len = src.len(); @@ -60,20 +59,12 @@ impl Parser { return Ok(None); } let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } idx += 8; len as usize } else { len as usize }; - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - let mask = if server { if chunk_len < idx + 4 { return Ok(None); @@ -98,11 +89,10 @@ impl Parser { max_size: usize, ) -> Result)>, ProtocolError> { // try to parse ws frame metadata - let (idx, finished, opcode, length, mask) = - match Parser::parse_metadata(src, server, max_size)? { - None => return Ok(None), - Some(res) => res, - }; + let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? { + None => return Ok(None), + Some(res) => res, + }; // not enough data if src.len() < idx + length { @@ -112,6 +102,13 @@ impl Parser { // remove prefix src.advance(idx); + // check for max allowed size + if length > max_size { + // drop the payload + src.advance(length); + return Err(ProtocolError::Overflow); + } + // no need for body if length == 0 { return Ok(Some((finished, opcode, None))); @@ -339,6 +336,30 @@ mod tests { } } + #[test] + fn test_parse_frame_max_size_recoverability() { + let mut buf = BytesMut::new(); + // The first text frame with length == 2, payload doesn't matter. + buf.extend(&[0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]); + // Next binary frame with length == 2 and payload == `[0x1111_1111u8, 0x1111_1111u8]`. + buf.extend(&[0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]); + + assert_eq!(buf.len(), 8); + assert!(matches!( + Parser::parse(&mut buf, false, 1), + Err(ProtocolError::Overflow) + )); + assert_eq!(buf.len(), 4); + let frame = extract(Parser::parse(&mut buf, false, 2)); + assert!(!frame.finished); + assert_eq!(frame.opcode, OpCode::Binary); + assert_eq!( + frame.payload, + Bytes::from(vec![0b1111_1111u8, 0b1111_1111u8]) + ); + assert_eq!(buf.len(), 0); + } + #[test] fn test_ping_frame() { let mut buf = BytesMut::new(); From 8dbf7da89feb4f2127f72b97b9d51b7e7847a067 Mon Sep 17 00:00:00 2001 From: PeterPierinakos <101414157+PeterPierinakos@users.noreply.github.com> Date: Sat, 25 Jun 2022 14:01:06 +0000 Subject: [PATCH 455/861] Fix common grammar mistakes and add small documentation for AppConfig's Default implementation (#2793) --- actix-web/src/app.rs | 2 +- actix-web/src/app_service.rs | 2 +- actix-web/src/config.rs | 10 ++++++++++ actix-web/src/guard.rs | 4 ++-- actix-web/src/handler.rs | 2 +- actix-web/src/response/response.rs | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 119980a03..213c8beff 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -60,7 +60,7 @@ where /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. /// /// # [`Data`] - /// Any [`Data`] type added here can utilize it's extractor implementation in handlers. + /// Any [`Data`] type added here can utilize its extractor implementation in handlers. /// Types not wrapped in `Data` cannot use this extractor. See [its docs](Data) for more /// about its usage and patterns. /// diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs index 3ef31ac75..28ff8c614 100644 --- a/actix-web/src/app_service.rs +++ b/actix-web/src/app_service.rs @@ -257,7 +257,7 @@ impl ServiceFactory for AppRoutingFactory { type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - // construct all services factory future with it's resource def and guards. + // construct all services factory future with its resource def and guards. let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let path = path.clone(); let guards = guards.borrow_mut().take().unwrap_or_default(); diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index dab309175..58a099c75 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -153,6 +153,16 @@ impl AppConfig { } impl Default for AppConfig { + /// Returns the default AppConfig. + /// Note: The included socket address is "127.0.0.1". + /// + /// 127.0.0.1: non-routable meta address that denotes an unknown, invalid or non-applicable target. + /// If you need a service only accessed by itself, use a loopback address. + /// A loopback address for IPv4 is any loopback address that begins with "127". + /// Loopback addresses should be only used to test your application locally. + /// The default configuration provides a loopback address. + /// + /// 0.0.0.0: if configured to use this special address, the application will listen to any IP address configured on the machine. fn default() -> Self { AppConfig::new( false, diff --git a/actix-web/src/guard.rs b/actix-web/src/guard.rs index 9f7514644..ef1301075 100644 --- a/actix-web/src/guard.rs +++ b/actix-web/src/guard.rs @@ -254,7 +254,7 @@ impl Guard for AllGuard { } } -/// Wraps a guard and inverts the outcome of it's `Guard` implementation. +/// Wraps a guard and inverts the outcome of its `Guard` implementation. /// /// # Examples /// The handler below will be called for any request method apart from `GET`. @@ -459,7 +459,7 @@ impl Guard for HostGuard { return scheme == req_host_uri_scheme; } - // TODO: is the the correct behavior? + // TODO: is this the correct behavior? // falls through if scheme cannot be determined } diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs index cf86cb38b..522a48b82 100644 --- a/actix-web/src/handler.rs +++ b/actix-web/src/handler.rs @@ -37,7 +37,7 @@ use crate::{ /// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the /// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on /// that tuple, and the `Handler::call` implementation for that particular function arity -/// destructures the tuple into it's component types and calls your handler function with them. +/// destructures the tuple into its component types and calls your handler function with them. /// /// In pseudo-code the process looks something like this: /// ```ignore diff --git a/actix-web/src/response/response.rs b/actix-web/src/response/response.rs index 630acc3f2..ead8badba 100644 --- a/actix-web/src/response/response.rs +++ b/actix-web/src/response/response.rs @@ -343,7 +343,7 @@ mod response_fut_impl { // Future is only implemented for BoxBody payload type because it's the most useful for making // simple handlers without async blocks. Making it generic over all MessageBody types requires a - // future impl on Response which would cause it's body field to be, undesirably, Option. + // future impl on Response which would cause its body field to be, undesirably, Option. // // This impl is not particularly efficient due to the Response construction and should probably // not be invoked if performance is important. Prefer an async fn/block in such cases. From 3d6ea7fe9b9027205ee88dd64c3d9c932240928e Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 26 Jun 2022 18:45:02 +0200 Subject: [PATCH 456/861] Improve documentation for `actix-web-actors` (#2788) --- actix-web-actors/Cargo.toml | 3 + actix-web-actors/src/context.rs | 56 +++++++++++++++- actix-web-actors/src/lib.rs | 55 ++++++++++++++++ actix-web-actors/src/ws.rs | 109 ++++++++++++++++++++++++++++---- 4 files changed, 210 insertions(+), 13 deletions(-) diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c939f6ab5..284351ed3 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,6 +29,9 @@ tokio = { version = "1.13.1", features = ["sync"] } actix-rt = "2.2" actix-test = "0.1.0-beta.13" awc = { version = "3", default-features = false } +actix-web = { version = "4", features = ["macros"] } + +mime = "0.3" env_logger = "0.9" 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 d83969ff7..f7b11c780 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -14,6 +14,58 @@ use futures_core::Stream; use tokio::sync::oneshot::Sender; /// Execution context for HTTP actors +/// +/// # Example +/// +/// A demonstration of [server-sent events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) using actors: +/// +/// ```no_run +/// use std::time::Duration; +/// +/// use actix::{Actor, AsyncContext}; +/// use actix_web::{get, http::header, App, HttpResponse, HttpServer}; +/// use actix_web_actors::HttpContext; +/// use bytes::Bytes; +/// +/// struct MyActor { +/// count: usize, +/// } +/// +/// impl Actor for MyActor { +/// type Context = HttpContext; +/// +/// fn started(&mut self, ctx: &mut Self::Context) { +/// ctx.run_later(Duration::from_millis(100), Self::write); +/// } +/// } +/// +/// impl MyActor { +/// fn write(&mut self, ctx: &mut HttpContext) { +/// self.count += 1; +/// if self.count > 3 { +/// ctx.write_eof() +/// } else { +/// ctx.write(Bytes::from(format!("event: count\ndata: {}\n\n", self.count))); +/// ctx.run_later(Duration::from_millis(100), Self::write); +/// } +/// } +/// } +/// +/// #[get("/")] +/// async fn index() -> HttpResponse { +/// HttpResponse::Ok() +/// .insert_header(header::ContentType(mime::TEXT_EVENT_STREAM)) +/// .streaming(HttpContext::create(MyActor { count: 0 })) +/// } +/// +/// #[actix_web::main] +/// async fn main() -> std::io::Result<()> { +/// HttpServer::new(|| App::new().service(index)) +/// .bind(("127.0.0.1", 8080))? +/// .run() +/// .await +/// } +/// ``` pub struct HttpContext
where A: Actor>, @@ -210,7 +262,7 @@ mod tests { type Context = HttpContext; fn started(&mut self, ctx: &mut Self::Context) { - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + ctx.run_later(Duration::from_millis(100), Self::write); } } @@ -221,7 +273,7 @@ mod tests { ctx.write_eof() } else { ctx.write(Bytes::from(format!("LINE-{}", self.count))); - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + ctx.run_later(Duration::from_millis(100), Self::write); } } } diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 70c957020..106bc5202 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,4 +1,59 @@ //! Actix actors support for Actix Web. +//! +//! # Examples +//! +//! ```no_run +//! use actix::{Actor, StreamHandler}; +//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +//! use actix_web_actors::ws; +//! +//! /// Define Websocket actor +//! struct MyWs; +//! +//! impl Actor for MyWs { +//! type Context = ws::WebsocketContext; +//! } +//! +//! /// Handler for ws::Message message +//! impl StreamHandler> for MyWs { +//! fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { +//! match msg { +//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), +//! Ok(ws::Message::Text(text)) => ctx.text(text), +//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin), +//! _ => (), +//! } +//! } +//! } +//! +//! #[get("/ws")] +//! async fn index(req: HttpRequest, stream: web::Payload) -> Result { +//! ws::start(MyWs, &req, stream) +//! } +//! +//! #[actix_web::main] +//! async fn main() -> std::io::Result<()> { +//! HttpServer::new(|| App::new().service(index)) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await +//! } +//! ``` +//! +//! # Documentation & Community Resources +//! In addition to this API documentation, several other resources are available: +//! +//! * [Website & User Guide](https://actix.rs/) +//! * [Documentation for `actix_web`](actix_web) +//! * [Examples Repository](https://github.com/actix/examples) +//! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x) +//! +//! To get started navigating the API docs, you may consider looking at the following pages first: +//! +//! * [`ws`]: This module provides actor support for WebSockets. +//! +//! * [`HttpContext`]: This struct provides actor support for streaming HTTP responses. +//! #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 6fde10192..9a4880159 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,4 +1,60 @@ //! Websocket integration. +//! +//! # Examples +//! +//! ```no_run +//! use actix::{Actor, StreamHandler}; +//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +//! use actix_web_actors::ws; +//! +//! /// Define Websocket actor +//! struct MyWs; +//! +//! impl Actor for MyWs { +//! type Context = ws::WebsocketContext; +//! } +//! +//! /// Handler for ws::Message message +//! impl StreamHandler> for MyWs { +//! fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { +//! match msg { +//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), +//! Ok(ws::Message::Text(text)) => ctx.text(text), +//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin), +//! _ => (), +//! } +//! } +//! } +//! +//! #[get("/ws")] +//! async fn websocket(req: HttpRequest, stream: web::Payload) -> Result { +//! ws::start(MyWs, &req, stream) +//! } +//! +//! const MAX_FRAME_SIZE: usize = 16_384; // 16KiB +//! +//! #[get("/custom-ws")] +//! async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result { +//! // Create a Websocket session with a specific max frame size, and protocols. +//! ws::WsResponseBuilder::new(MyWs, &req, stream) +//! .frame_size(MAX_FRAME_SIZE) +//! .protocols(&["A", "B"]) +//! .start() +//! } +//! +//! #[actix_web::main] +//! async fn main() -> std::io::Result<()> { +//! HttpServer::new(|| { +//! App::new() +//! .service(websocket) +//! .service(custom_websocket) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await +//! } +//! ``` +//! use std::{ collections::VecDeque, @@ -41,20 +97,51 @@ use tokio::sync::oneshot; /// /// # Examples /// -/// Create a Websocket session response with default configuration. -/// ```ignore -/// WsResponseBuilder::new(WsActor, &req, stream).start() -/// ``` +/// ```no_run +/// # use actix::{Actor, StreamHandler}; +/// # use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +/// # use actix_web_actors::ws; +/// # +/// # struct MyWs; +/// # +/// # impl Actor for MyWs { +/// # type Context = ws::WebsocketContext; +/// # } +/// # +/// # /// Handler for ws::Message message +/// # impl StreamHandler> for MyWs { +/// # fn handle(&mut self, msg: Result, ctx: &mut Self::Context) {} +/// # } +/// # +/// #[get("/ws")] +/// async fn websocket(req: HttpRequest, stream: web::Payload) -> Result { +/// ws::WsResponseBuilder::new(MyWs, &req, stream).start() +/// } /// -/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols. -/// ```ignore /// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB /// -/// ws::WsResponseBuilder::new(WsActor, &req, stream) -/// .codec(Codec::new()) -/// .protocols(&["A", "B"]) -/// .frame_size(MAX_FRAME_SIZE) -/// .start() +/// #[get("/custom-ws")] +/// async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result { +/// // Create a Websocket session with a specific max frame size, codec, and protocols. +/// ws::WsResponseBuilder::new(MyWs, &req, stream) +/// .codec(actix_http::ws::Codec::new()) +/// // This will overwrite the codec's max frame-size +/// .frame_size(MAX_FRAME_SIZE) +/// .protocols(&["A", "B"]) +/// .start() +/// } +/// # +/// # #[actix_web::main] +/// # async fn main() -> std::io::Result<()> { +/// # HttpServer::new(|| { +/// # App::new() +/// # .service(websocket) +/// # .service(custom_websocket) +/// # }) +/// # .bind(("127.0.0.1", 8080))? +/// # .run() +/// # .await +/// # } /// ``` pub struct WsResponseBuilder<'a, A, T> where From f7d7d92984f0ed9204c7e69a8e16d0582a7efdcd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 27 Jun 2022 03:12:36 +0100 Subject: [PATCH 457/861] address clippy lints --- actix-http/src/body/message_body.rs | 1 + actix-http/src/responses/head.rs | 4 ++-- actix-web/src/types/path.rs | 1 + actix-web/src/types/payload.rs | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 9090e34d5..ab742b9cd 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -481,6 +481,7 @@ mod tests { assert_poll_next_none!(pl); } + #[allow(clippy::let_unit_value)] #[actix_rt::test] async fn test_unit() { let pl = (); diff --git a/actix-http/src/responses/head.rs b/actix-http/src/responses/head.rs index cb47c4b7a..01bca6c5b 100644 --- a/actix-http/src/responses/head.rs +++ b/actix-http/src/responses/head.rs @@ -237,7 +237,7 @@ mod tests { .await; let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream + stream .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n") .unwrap(); let mut data = vec![]; @@ -251,7 +251,7 @@ mod tests { assert!(memmem::find(&data, b"content-length").is_none()); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream + stream .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n") .unwrap(); let mut data = vec![]; diff --git a/actix-web/src/types/path.rs b/actix-web/src/types/path.rs index 0fcac2c19..34c335ba9 100644 --- a/actix-web/src/types/path.rs +++ b/actix-web/src/types/path.rs @@ -183,6 +183,7 @@ mod tests { assert!(Path::::from_request(&req, &mut pl).await.is_err()); } + #[allow(clippy::let_unit_value)] #[actix_rt::test] async fn test_tuple_extract() { let resource = ResourceDef::new("/{key}/{value}/"); diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs index b47a39e97..af195b867 100644 --- a/actix-web/src/types/payload.rs +++ b/actix-web/src/types/payload.rs @@ -113,7 +113,7 @@ pub struct BytesExtractFut { body_fut: HttpMessageBody, } -impl<'a> Future for BytesExtractFut { +impl Future for BytesExtractFut { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -167,7 +167,7 @@ pub struct StringExtractFut { encoding: &'static Encoding, } -impl<'a> Future for StringExtractFut { +impl Future for StringExtractFut { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { From 0dba6310c66c3a0fd5bc02b072fc1ec696814bbf Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 27 Jun 2022 04:57:21 +0200 Subject: [PATCH 458/861] Expose option for setting TLS handshake timeout (#2752) Co-authored-by: Rob Ede --- actix-http/src/lib.rs | 3 ++ actix-http/src/service.rs | 65 +++++++++++++++++++++++++++++++- actix-http/tests/test_openssl.rs | 9 +++-- actix-http/tests/test_rustls.rs | 8 +++- actix-web/CHANGES.md | 7 +++- actix-web/src/server.rs | 39 ++++++++++++++++++- 6 files changed, 121 insertions(+), 10 deletions(-) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 360cb86fc..184049860 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -25,6 +25,7 @@ )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_cfg))] pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -69,6 +70,8 @@ pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; +#[cfg(any(feature = "openssl", feature = "rustls"))] +pub use self::service::TlsAcceptorConfig; /// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f4fe625a3..27029cb8e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -181,6 +181,25 @@ where } } +/// Configuration options used when accepting TLS connection. +#[cfg(any(feature = "openssl", feature = "rustls"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] +#[derive(Debug, Default)] +pub struct TlsAcceptorConfig { + pub(crate) handshake_timeout: Option, +} + +#[cfg(any(feature = "openssl", feature = "rustls"))] +impl TlsAcceptorConfig { + /// Set TLS handshake timeout duration. + pub fn handshake_timeout(self, dur: std::time::Duration) -> Self { + Self { + handshake_timeout: Some(dur), + // ..self + } + } +} + #[cfg(feature = "openssl")] mod openssl { use actix_service::ServiceFactoryExt as _; @@ -230,7 +249,28 @@ mod openssl { Error = TlsError, InitError = (), > { - Acceptor::new(acceptor) + self.openssl_with_config(acceptor, TlsAcceptorConfig::default()) + } + + /// Create OpenSSL based service with custom TLS acceptor configuration. + pub fn openssl_with_config( + self, + acceptor: SslAcceptor, + tls_acceptor_config: TlsAcceptorConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + let mut acceptor = Acceptor::new(acceptor); + + if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout { + acceptor.set_handshake_timeout(handshake_timeout); + } + + acceptor .map_init_err(|_| { unreachable!("TLS acceptor service factory does not error on init") }) @@ -293,8 +333,23 @@ mod rustls { { /// Create Rustls based service. pub fn rustls( + self, + config: ServerConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + self.rustls_with_config(config, TlsAcceptorConfig::default()) + } + + /// Create Rustls based service with custom TLS acceptor configuration. + pub fn rustls_with_config( self, mut config: ServerConfig, + tls_acceptor_config: TlsAcceptorConfig, ) -> impl ServiceFactory< TcpStream, Config = (), @@ -306,7 +361,13 @@ mod rustls { protos.extend_from_slice(&config.alpn_protocols); config.alpn_protocols = protos; - Acceptor::new(config) + let mut acceptor = Acceptor::new(config); + + if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout { + acceptor.set_handshake_timeout(handshake_timeout); + } + + acceptor .map_init_err(|_| { unreachable!("TLS acceptor service factory does not error on init") }) diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 35321ac98..b97b2e45b 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -2,13 +2,13 @@ extern crate tls_openssl as openssl; -use std::{convert::Infallible, io}; +use std::{convert::Infallible, io, time::Duration}; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, header::{self, HeaderValue}, - Error, HttpService, Method, Request, Response, StatusCode, Version, + Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version, }; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -89,7 +89,10 @@ async fn h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::ok()) }) - .openssl(tls_config()) + .openssl_with_config( + tls_config(), + TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), + ) .map_err(|_| ()) }) .await; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 550375296..2bbf1524b 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -8,13 +8,14 @@ use std::{ net::{SocketAddr, TcpStream as StdTcpStream}, sync::Arc, task::Poll, + time::Duration, }; use actix_http::{ body::{BodyStream, BoxBody, SizedStream}, error::PayloadError, header::{self, HeaderName, HeaderValue}, - Error, HttpService, Method, Request, Response, StatusCode, Version, + Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version, }; use actix_http_test::test_server; use actix_rt::pin; @@ -160,7 +161,10 @@ async fn h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::ok()) }) - .rustls(tls_config()) + .rustls_with_config( + tls_config(), + TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), + ) }) .await; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 3fa2f8f21..0144cb912 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,12 +1,17 @@ # Changelog ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. ### Added - Add `ServiceRequest::{parts, request}()` getter methods. [#2786] +- Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752] +### Changed +- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. + +[#2752]: https://github.com/actix/actix-web/pull/2752 [#2786]: https://github.com/actix/actix-web/pull/2786 + ## 4.1.0 - 2022-06-11 ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 99812600c..169eafab0 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -18,6 +18,9 @@ use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorB #[cfg(feature = "rustls")] use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig; +#[cfg(any(feature = "openssl", feature = "rustls"))] +use actix_http::TlsAcceptorConfig; + use crate::{config::AppConfig, Error}; struct Socket { @@ -30,6 +33,8 @@ struct Config { keep_alive: KeepAlive, client_request_timeout: Duration, client_disconnect_timeout: Duration, + #[cfg(any(feature = "openssl", feature = "rustls"))] + tls_handshake_timeout: Option, } /// An HTTP Server. @@ -92,6 +97,8 @@ where keep_alive: KeepAlive::default(), client_request_timeout: Duration::from_secs(5), client_disconnect_timeout: Duration::from_secs(1), + #[cfg(any(feature = "rustls", feature = "openssl"))] + tls_handshake_timeout: None, })), backlog: 1024, sockets: Vec::new(), @@ -225,6 +232,24 @@ where self } + /// Set TLS handshake timeout. + /// + /// Defines a timeout for TLS handshake. If the TLS handshake does not complete + /// within this time, the connection is closed. + /// + /// By default handshake timeout is set to 3000 milliseconds. + #[cfg(any(feature = "openssl", feature = "rustls"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] + pub fn tls_handshake_timeout(self, dur: Duration) -> Self { + self.config + .lock() + .unwrap() + .tls_handshake_timeout + .replace(dur); + + self + } + #[doc(hidden)] #[deprecated(since = "4.0.0", note = "Renamed to `client_disconnect_timeout`.")] pub fn client_shutdown(self, dur: u64) -> Self { @@ -376,10 +401,15 @@ where .into_factory() .map_err(|err| err.into().error_response()); + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) - .openssl(acceptor.clone()) + .openssl_with_config(acceptor.clone(), acceptor_config) })?; Ok(self) @@ -434,10 +464,15 @@ where .into_factory() .map_err(|err| err.into().error_response()); + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + svc.finish(map_config(fac, move |_| { AppConfig::new(true, host.clone(), addr) })) - .rustls(config.clone()) + .rustls_with_config(config.clone(), acceptor_config) })?; Ok(self) From 06c79458012457b2ef6e106b7fbe283a63683e73 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 30 Jun 2022 09:19:16 +0100 Subject: [PATCH 459/861] retain previously set vary headers when using compress (#2798) * retain previously set vary headers when using compress --- actix-http/CHANGES.md | 8 +++++--- actix-http/src/encoding/encoder.rs | 2 +- actix-web/src/middleware/compress.rs | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index dd6051b85..c9ab687f6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,13 +1,15 @@ # Changes ## Unreleased - 2022-xx-xx -### Fixed -- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] - ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +### Fixed +- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790] +- Retain previously set Vary headers when using compression encoder. [#2798] + [#2790]: https://github.com/actix/actix-web/pull/2790 +[#2798]: https://github.com/actix/actix-web/pull/2798 ## 3.1.0 - 2022-06-11 diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0bbb1c106..bbe53e8e8 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -257,7 +257,7 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { head.headers_mut() .insert(header::CONTENT_ENCODING, encoding.to_header_value()); head.headers_mut() - .insert(header::VARY, HeaderValue::from_static("accept-encoding")); + .append(header::VARY, HeaderValue::from_static("accept-encoding")); head.no_chunking(false); } diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index 4fdd74779..ed4291bed 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -251,6 +251,8 @@ static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { #[cfg(feature = "compress-gzip")] #[cfg(test)] mod tests { + use std::collections::HashSet; + use super::*; use crate::{middleware::DefaultHeaders, test, web, App}; @@ -305,4 +307,27 @@ mod tests { let bytes = test::read_body(res).await; assert_eq!(gzip_decode(bytes), DATA.as_bytes()); } + + #[actix_rt::test] + async fn retains_previously_set_vary_header() { + let app = test::init_service({ + App::new() + .wrap(Compress::default()) + .default_service(web::to(move || { + HttpResponse::Ok() + .insert_header((header::VARY, "x-test")) + .finish() + })) + }) + .await; + + let req = test::TestRequest::default() + .insert_header((header::ACCEPT_ENCODING, "gzip")) + .to_request(); + let res = test::call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::OK); + let vary_headers = res.headers().get_all(header::VARY).collect::>(); + assert!(vary_headers.contains(&HeaderValue::from_static("x-test"))); + assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding"))); + } } From c6eba2da9b6ead6112433f7d2aaa1f2d19a22395 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 06:16:17 +0100 Subject: [PATCH 460/861] prepare actix-http release 3.2.0 (#2801) --- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c9ab687f6..024a731d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.2.0 - 2022-06-30 ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2a4966884..3fccbfa03 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.1.0" +version = "3.2.0" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 211f433e8..3179258af 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.1.0)](https://docs.rs/actix-http/3.1.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.0)](https://docs.rs/actix-http/3.2.0) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) +[![dependency status](https://deps.rs/crate/actix-http/3.2.0/status.svg)](https://deps.rs/crate/actix-http/3.2.0) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8f9a12ed5d30c1f0a44cbcb5dfb0c3fecd1f6348 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 08:23:40 +0100 Subject: [PATCH 461/861] fix parsing ambiguities for HTTP/1.0 requests (#2794) * fix HRS vuln when first CL header is 0 * ignore TE headers in http/1.0 reqs * update changelog * disallow HTTP/1.0 requests without a CL header * fix test * broken fix for http1.0 post requests --- actix-http/CHANGES.md | 4 + actix-http/src/h1/decoder.rs | 156 +++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 024a731d8..5d441919d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests. [#2794] + +[#2794]: https://github.com/actix/actix-web/pull/2794 ## 3.2.0 - 2022-06-30 diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index a9443997e..91a93d22c 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -46,6 +46,23 @@ pub(crate) enum PayloadLength { None, } +impl PayloadLength { + /// Returns true if variant is `None`. + fn is_none(&self) -> bool { + matches!(self, Self::None) + } + + /// Returns true if variant is represents zero-length (not none) payload. + fn is_zero(&self) -> bool { + matches!( + self, + PayloadLength::Payload(PayloadType::Payload(PayloadDecoder { + kind: Kind::Length(0) + })) + ) + } +} + pub(crate) trait MessageType: Sized { fn set_connection_type(&mut self, conn_type: Option); @@ -59,6 +76,7 @@ pub(crate) trait MessageType: Sized { &mut self, slice: &Bytes, raw_headers: &[HeaderIndex], + version: Version, ) -> Result { let mut ka = None; let mut has_upgrade_websocket = false; @@ -87,21 +105,23 @@ pub(crate) trait MessageType: Sized { return Err(ParseError::Header); } - header::CONTENT_LENGTH => match value.to_str() { - Ok(s) if s.trim().starts_with('+') => { - debug!("illegal Content-Length: {:?}", s); + header::CONTENT_LENGTH => match value.to_str().map(str::trim) { + Ok(val) if val.starts_with('+') => { + debug!("illegal Content-Length: {:?}", val); return Err(ParseError::Header); } - Ok(s) => { - if let Ok(len) = s.parse::() { - if len != 0 { - content_length = Some(len); - } + + Ok(val) => { + if let Ok(len) = val.parse::() { + // accept 0 lengths here and remove them in `decode` after all + // headers have been processed to prevent request smuggling issues + content_length = Some(len); } else { - debug!("illegal Content-Length: {:?}", s); + debug!("illegal Content-Length: {:?}", val); return Err(ParseError::Header); } } + Err(_) => { debug!("illegal Content-Length: {:?}", value); return Err(ParseError::Header); @@ -114,22 +134,23 @@ pub(crate) trait MessageType: Sized { return Err(ParseError::Header); } - header::TRANSFER_ENCODING => { + header::TRANSFER_ENCODING if version == Version::HTTP_11 => { seen_te = true; - if let Ok(s) = value.to_str().map(str::trim) { - if s.eq_ignore_ascii_case("chunked") { + if let Ok(val) = value.to_str().map(str::trim) { + if val.eq_ignore_ascii_case("chunked") { chunked = true; - } else if s.eq_ignore_ascii_case("identity") { + } else if val.eq_ignore_ascii_case("identity") { // allow silently since multiple TE headers are already checked } else { - debug!("illegal Transfer-Encoding: {:?}", s); + debug!("illegal Transfer-Encoding: {:?}", val); return Err(ParseError::Header); } } else { return Err(ParseError::Header); } } + // connection keep-alive state header::CONNECTION => { ka = if let Ok(conn) = value.to_str().map(str::trim) { @@ -146,6 +167,7 @@ pub(crate) trait MessageType: Sized { None }; } + header::UPGRADE => { if let Ok(val) = value.to_str().map(str::trim) { if val.eq_ignore_ascii_case("websocket") { @@ -153,19 +175,23 @@ pub(crate) trait MessageType: Sized { } } } + header::EXPECT => { let bytes = value.as_bytes(); if bytes.len() >= 4 && &bytes[0..4] == b"100-" { expect = true; } } + _ => {} } headers.append(name, value); } } + self.set_connection_type(ka); + if expect { self.set_expect() } @@ -249,7 +275,22 @@ impl MessageType for Request { let mut msg = Request::new(); // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let mut length = + msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?; + + // disallow HTTP/1.0 POST requests that do not contain a Content-Length headers + // see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2 + if ver == Version::HTTP_10 && method == Method::POST && length.is_none() { + debug!("no Content-Length specified for HTTP/1.0 POST request"); + return Err(ParseError::Header); + } + + // Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed. + // Protects against some request smuggling attacks. + // See https://github.com/actix/actix-web/issues/2767. + if length.is_zero() { + length = PayloadLength::None; + } // payload decoder let decoder = match length { @@ -337,7 +378,15 @@ impl MessageType for ResponseHead { msg.version = ver; // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let mut length = + msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?; + + // Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed. + // Protects against some request smuggling attacks. + // See https://github.com/actix/actix-web/issues/2767. + if length.is_zero() { + length = PayloadLength::None; + } // message payload let decoder = if let PayloadLength::Payload(pl) = length { @@ -606,14 +655,40 @@ mod tests { } #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); + fn parse_h10_post() { + let mut buf = BytesMut::from( + "POST /test1 HTTP/1.0\r\n\ + Content-Length: 3\r\n\ + \r\n\ + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test1"); + + let mut buf = BytesMut::from( + "POST /test2 HTTP/1.0\r\n\ + Content-Length: 0\r\n\ + \r\n", + ); let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); + + let mut buf = BytesMut::from( + "POST /test3 HTTP/1.0\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + let err = reader.decode(&mut buf).unwrap_err(); + assert!(err.to_string().contains("Header")) } #[test] @@ -981,6 +1056,17 @@ mod tests { ); expect_parse_err!(&mut buf); + + let mut buf = BytesMut::from( + "GET / HTTP/1.1\r\n\ + Host: example.com\r\n\ + Content-Length: 0\r\n\ + Content-Length: 2\r\n\ + \r\n\ + ab", + ); + + expect_parse_err!(&mut buf); } #[test] @@ -996,6 +1082,40 @@ mod tests { expect_parse_err!(&mut buf); } + #[test] + fn hrs_te_http10() { + // in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header + + let mut buf = BytesMut::from( + "POST / HTTP/1.0\r\n\ + Host: example.com\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 3\r\n\ + aaa\r\n\ + 0\r\n\ + ", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn hrs_cl_and_te_http10() { + // in HTTP/1.0 transfer encoding is simply ignored so it's fine to have both + + let mut buf = BytesMut::from( + "GET / HTTP/1.0\r\n\ + Host: example.com\r\n\ + Content-Length: 3\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 000", + ); + + parse_ready!(&mut buf); + } + #[test] fn hrs_unknown_transfer_encoding() { let mut buf = BytesMut::from( From 7e990e423fdf4b6b4b8cf24a7bc412351d8e995b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 08:24:45 +0100 Subject: [PATCH 462/861] add http/1.0 GET parsing tests --- actix-http/src/h1/decoder.rs | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 91a93d22c..c718f26a8 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -654,6 +654,46 @@ mod tests { assert_eq!(req.path(), "/test"); } + #[test] + fn parse_h10_get() { + let mut buf = BytesMut::from( + "GET /test1 HTTP/1.0\r\n\ + \r\n\ + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test1"); + + let mut buf = BytesMut::from( + "GET /test2 HTTP/1.0\r\n\ + Content-Length: 0\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test2"); + + let mut buf = BytesMut::from( + "GET /test3 HTTP/1.0\r\n\ + Content-Length: 3\r\n\ + \r\n + abc", + ); + + let mut reader = MessageDecoder::::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test3"); + } + #[test] fn parse_h10_post() { let mut buf = BytesMut::from( From e524fc86eaef37c36b40c25c20b9880893c76508 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 09:03:57 +0100 Subject: [PATCH 463/861] add HTTP/0.9 rejection test --- actix-http/src/h1/decoder.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index c718f26a8..edfc00fd6 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -654,12 +654,32 @@ mod tests { assert_eq!(req.path(), "/test"); } + #[test] + fn parse_h09_reject() { + let mut buf = BytesMut::from( + "GET /test1 HTTP/0.9\r\n\ + \r\n", + ); + + let mut reader = MessageDecoder::::default(); + reader.decode(&mut buf).unwrap_err(); + + let mut buf = BytesMut::from( + "POST /test2 HTTP/0.9\r\n\ + Content-Length: 3\r\n\ + \r\n + abc", + ); + + let mut reader = MessageDecoder::::default(); + reader.decode(&mut buf).unwrap_err(); + } + #[test] fn parse_h10_get() { let mut buf = BytesMut::from( "GET /test1 HTTP/1.0\r\n\ - \r\n\ - abc", + \r\n", ); let mut reader = MessageDecoder::::default(); From 226ea696ce43283d259cdea316fcc9efca6e25b9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 10:19:28 +0100 Subject: [PATCH 464/861] update dev deps --- actix-http/Cargo.toml | 4 ++-- actix-web/Cargo.toml | 6 +++--- awc/Cargo.toml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3fccbfa03..abb1e4603 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -108,10 +108,10 @@ env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" once_cell = "1.9" -rcgen = "0.8" +rcgen = "0.9" regex = "1.3" rustversion = "1" -rustls-pemfile = "0.2" +rustls-pemfile = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" static_assertions = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 8cdf0f611..c58e1604b 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -105,14 +105,14 @@ actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" -const-str = "0.3" +const-str = "0.4" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } rand = "0.8" -rcgen = "0.8" -rustls-pemfile = "0.2" +rcgen = "0.9" +rustls-pemfile = "1" serde = { version = "1.0", features = ["derive"] } static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ba0fc14e3..1a69fd49e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -102,13 +102,13 @@ actix-utils = "3" actix-web = { version = "4", features = ["openssl"] } brotli = "3.3.3" -const-str = "0.3" +const-str = "0.4" env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } static_assertions = "1.1" -rcgen = "0.8" -rustls-pemfile = "0.2" +rcgen = "0.9" +rustls-pemfile = "1" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } zstd = "0.11" From df5257c3730ccf270dbe0799501826fc2d4a1072 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 10:21:46 +0100 Subject: [PATCH 465/861] update trust dns resolver --- awc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1a69fd49e..9da103cb0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -90,7 +90,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } -trust-dns-resolver = { version = "0.20.0", optional = true } +trust-dns-resolver = { version = "0.21", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } From b62f1b4ef74fc28881bdf070e35395065189bd26 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 12:40:00 +0100 Subject: [PATCH 466/861] migrate deprecated method in docs --- actix-router/src/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index bc082273c..3c6754aeb 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -649,7 +649,7 @@ impl ResourceDef { /// resource.capture_match_info_fn( /// path, /// // when env var is not set, reject when path contains "admin" - /// |res| !(!admin_allowed && res.path().contains("admin")), + /// |path| !(!admin_allowed && path.as_str().contains("admin")), /// ) /// } /// From 987067698b11db82f113ae350356cb11d1d7d1f0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 1 Jul 2022 12:45:26 +0100 Subject: [PATCH 467/861] use sparse registry in CI --- .github/workflows/ci-post-merge.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 9fce98f4c..d8752d8aa 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -23,6 +23,7 @@ jobs: CI: 1 CARGO_INCREMENTAL: 0 VCPKGRS_DYNAMIC: 1 + CARGO_UNSTABLE_SPARSE_REGISTRY: true steps: - uses: actions/checkout@v2 @@ -86,6 +87,11 @@ jobs: ci_feature_powerset_check: name: Verify Feature Combinations runs-on: ubuntu-latest + + env: + CI: 1 + CARGO_INCREMENTAL: 0 + steps: - uses: actions/checkout@v2 @@ -119,6 +125,11 @@ jobs: nextest: name: nextest runs-on: ubuntu-latest + + env: + CI: 1 + CARGO_INCREMENTAL: 0 + steps: - uses: actions/checkout@v2 From f3f41a0cc70e43564f8243b3ff425195566b5f16 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 16:50:54 +0100 Subject: [PATCH 468/861] prepare actix-http release 3.2.1 --- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5d441919d..7e6604046 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 3.2.1 - 2022-07-02 ### Fixed - Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests. [#2794] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index abb1e4603..03767ca4e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.2.0" +version = "3.2.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 3179258af..787d2f653 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.0)](https://docs.rs/actix-http/3.2.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.1)](https://docs.rs/actix-http/3.2.1) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.2.0/status.svg)](https://deps.rs/crate/actix-http/3.2.0) +[![dependency status](https://deps.rs/crate/actix-http/3.2.1/status.svg)](https://deps.rs/crate/actix-http/3.2.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 2f79daec1608f079802b86a0ff452f380a62a67e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:05:48 +0100 Subject: [PATCH 469/861] only run tests on stable --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49ad25ccf..15d98aae7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: with: { command: ci-check-default } - name: tests + if: matrix.version == 'stable' # temp? timeout-minutes: 60 run: | cargo test --lib --tests -p=actix-router --all-features From e0845d9ad9540818e2a6eec170fa2028d31bf607 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:12:24 +0100 Subject: [PATCH 470/861] add msrv workarounds to ci --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15d98aae7..10ba660f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,12 @@ jobs: profile: minimal override: true + - name: workaround MSRV issues + if: matrix.version != 'stable' + run: | + cargo add const-str@0.3 --dev -p=actix-web + cargo add const-str@0.3 --dev -p=awc + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } @@ -68,7 +74,6 @@ jobs: with: { command: ci-check-default } - name: tests - if: matrix.version == 'stable' # temp? timeout-minutes: 60 run: | cargo test --lib --tests -p=actix-router --all-features From f7d629a61ae04cc5495da4b4d5daabd254396215 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:20:46 +0100 Subject: [PATCH 471/861] fix cargo-add in CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10ba660f3..35a829ee7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | + cargo install cargo-add cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 23ef51609e1e6797ae1905620421f6ebf758a0d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:29:06 +0100 Subject: [PATCH 472/861] s/cargo-add/cargo-edit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35a829ee7..f6fafc67c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | - cargo install cargo-add + cargo install cargo-edit cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 9a2f8450e0bfa8c15db6fe8447a7b25606eb2c8e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 17:40:03 +0100 Subject: [PATCH 473/861] install older cargo-edit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6fafc67c..cb103e21a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: workaround MSRV issues if: matrix.version != 'stable' run: | - cargo install cargo-edit + cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc From 8e2ae8cd40763cbec6bf78a6c9ae35ae0deb01cc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 18:38:08 +0100 Subject: [PATCH 474/861] install nextest faster --- .github/workflows/ci-post-merge.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d8752d8aa..008ba3296 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -146,11 +146,8 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - - name: Install cargo-nextest - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-nextest + - name: Install nextest + uses: taiki-e/install-action@nextest - name: Test with cargo-nextest uses: actions-rs/cargo@v1 From 9b51624b2707cb33abad329bd8c5f7ed9ca6ef20 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 18:43:19 +0100 Subject: [PATCH 475/861] update cargo-cache to 0.8.2 --- .github/workflows/ci-post-merge.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 008ba3296..430c3b1d8 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -81,7 +81,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean cargo-cache ci_feature_powerset_check: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb103e21a..c956c2f0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean cargo-cache io-uring: From 75517cce822f9a78eea507229743fbab338eb740 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 20:00:59 +0100 Subject: [PATCH 476/861] install cargo hack in CI faster --- .github/workflows/ci-post-merge.yml | 24 +++++++++--------------- .github/workflows/ci.yml | 9 +++------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 430c3b1d8..1ee97b591 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -45,18 +45,15 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } @@ -102,18 +99,15 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check feature combinations uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset } @@ -140,15 +134,15 @@ jobs: profile: minimal override: true + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: { command: generate-lockfile } - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - - name: Install nextest - uses: taiki-e/install-action@nextest - - name: Test with cargo-nextest uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c956c2f0e..2ea920808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,9 @@ jobs: profile: minimal override: true + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: workaround MSRV issues if: matrix.version != 'stable' run: | @@ -60,12 +63,6 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } From 40eab1f091c03ac00c0f1333b2a7a8e336f9f7e8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 20:07:27 +0100 Subject: [PATCH 477/861] simplify simple decoder tests --- actix-http/src/h1/decoder.rs | 151 +++++++++++++---------------------- 1 file changed, 54 insertions(+), 97 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index edfc00fd6..40cabf9cb 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -844,121 +844,98 @@ mod tests { #[test] fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET /test HTTP/1.0\r\n\r\n")); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET /test HTTP/1.1\r\n\r\n")); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_close() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: Close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_close_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", - ); - - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: Keep-Alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_other_1_0() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] fn test_conn_other_1_1() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - + )); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] fn test_conn_upgrade() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ upgrade: websockets\r\n\ connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ upgrade: Websockets\r\n\ connection: Upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); @@ -966,59 +943,54 @@ mod tests { #[test] fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "CONNECT /test HTTP/1.1\r\n\ content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert!(req.upgrade()); } #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( + fn test_headers_bad_content_length() { + // string CL + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: line\r\n\r\n", - ); + )); - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( + // negative CL + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: -1\r\n\r\n", - ); + )); - expect_parse_err!(&mut buf); + // octal CL + // expect_parse_err!(&mut BytesMut::from( + // "GET /test HTTP/1.1\r\n\ + // content-length: 0123\r\n\r\n", + // )); } #[test] fn test_invalid_header() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn test_invalid_name() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("getpath \r\n\r\n")); } #[test] @@ -1058,11 +1030,10 @@ mod tests { #[test] fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( + let req = parse_ready!(&mut BytesMut::from( "GET /test HTTP/1.1\r\n\ x-test: тест\r\n\r\n", - ); - let req = parse_ready!(&mut buf); + )); assert_eq!( req.headers().get("x-test").unwrap().as_bytes(), @@ -1072,24 +1043,18 @@ mod tests { #[test] fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - + let req = parse_ready!(&mut BytesMut::from("GET //path HTTP/1.1\r\n\r\n")); assert_eq!(req.path(), "//path"); } #[test] fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n")); } #[test] fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); + expect_parse_err!(&mut BytesMut::from("GET //get HT/11\r\n\r\n")); } #[test] @@ -1106,47 +1071,41 @@ mod tests { #[test] fn hrs_multiple_content_length() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: 4\r\n\ Content-Length: 2\r\n\ \r\n\ abcd", - ); + )); - expect_parse_err!(&mut buf); - - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: 0\r\n\ Content-Length: 2\r\n\ \r\n\ ab", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn hrs_content_length_plus() { - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "GET / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: +3\r\n\ \r\n\ 000", - ); - - expect_parse_err!(&mut buf); + )); } #[test] fn hrs_te_http10() { // in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header - let mut buf = BytesMut::from( + expect_parse_err!(&mut BytesMut::from( "POST / HTTP/1.0\r\n\ Host: example.com\r\n\ Transfer-Encoding: chunked\r\n\ @@ -1155,9 +1114,7 @@ mod tests { aaa\r\n\ 0\r\n\ ", - ); - - expect_parse_err!(&mut buf); + )); } #[test] From c0d5d7bdb54a1983942d5a25056d4c669eb03b51 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jul 2022 21:04:37 +0100 Subject: [PATCH 478/861] add octal-ish CL test --- actix-http/src/h1/decoder.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 40cabf9cb..203b6c531 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -964,12 +964,20 @@ mod tests { "GET /test HTTP/1.1\r\n\ content-length: -1\r\n\r\n", )); + } - // octal CL - // expect_parse_err!(&mut BytesMut::from( - // "GET /test HTTP/1.1\r\n\ - // content-length: 0123\r\n\r\n", - // )); + #[test] + fn octal_ish_cl_parsed_as_decimal() { + let mut buf = BytesMut::from( + "POST /test HTTP/1.1\r\n\ + content-length: 011\r\n\r\n", + ); + let mut reader = MessageDecoder::::default(); + let (_req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + assert!(matches!( + pl, + PayloadType::Payload(pl) if pl == PayloadDecoder::length(11) + )); } #[test] From 8759d79b03510f0cae2aac1cb459e79802c85fc6 Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Mon, 4 Jul 2022 06:31:49 +0200 Subject: [PATCH 479/861] `routes` macro allowing multiple paths per handler (#2718) * WIP: basic implementation for `routes` macro * chore: changelog, docs, tests * error on missing methods * Apply suggestions from code review Co-authored-by: Igor Aleksanov * update test stderr expectation * add additional tests * fix stderr output * remove useless ResourceType this is dead code from back when .to and .to_async were different ways to add a service Co-authored-by: Igor Aleksanov Co-authored-by: Rob Ede --- actix-web-codegen/CHANGES.md | 3 + actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 48 ++- actix-web-codegen/src/route.rs | 274 +++++++++++------- actix-web-codegen/tests/test_macro.rs | 76 ++++- actix-web-codegen/tests/trybuild.rs | 4 + .../trybuild/routes-missing-args-fail.rs | 14 + .../trybuild/routes-missing-args-fail.stderr | 21 ++ .../trybuild/routes-missing-method-fail.rs | 13 + .../routes-missing-method-fail.stderr | 15 + actix-web-codegen/tests/trybuild/routes-ok.rs | 23 ++ actix-web/CHANGES.md | 2 + actix-web/src/lib.rs | 1 + 13 files changed, 384 insertions(+), 112 deletions(-) create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs create mode 100644 actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr create mode 100644 actix-web-codegen/tests/trybuild/routes-ok.rs diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index a85d6c454..6b525a441 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2022-xx-xx +- Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2718]: https://github.com/actix/actix-web/pull/2718 + ## 4.0.1 - 2022-06-11 - Fix support for guard paths in route handler macros. [#2771] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 52094443b..a2aac7e68 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true actix-router = "0.5.0" proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["full", "parsing"] } +syn = { version = "1", features = ["full", "extra-traits"] } [dev-dependencies] actix-macros = "0.2.3" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 5ca5616b6..4b6dc43c5 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -46,9 +46,20 @@ //! ``` //! //! # Multiple Path Handlers -//! There are no macros to generate multi-path handlers. Let us know in [this issue]. +//! Acts as a wrapper for multiple single method handler macros. It takes no arguments and +//! delegates those to the macros for the individual methods. See [macro@routes] macro docs. //! -//! [this issue]: https://github.com/actix/actix-web/issues/1709 +//! ``` +//! # use actix_web::HttpResponse; +//! # use actix_web_codegen::routes; +//! #[routes] +//! #[get("/test")] +//! #[get("/test2")] +//! #[delete("/test")] +//! async fn example() -> HttpResponse { +//! HttpResponse::Ok().finish() +//! } +//! ``` //! //! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes //! [GET]: macro@get @@ -104,6 +115,39 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { route::with_method(None, args, input) } +/// Creates resource handler, allowing multiple HTTP methods and paths. +/// +/// # Syntax +/// ```plain +/// #[routes] +/// #[("path", ...)] +/// #[("path", ...)] +/// ... +/// ``` +/// +/// # Attributes +/// The `routes` macro itself has no parameters, but allows specifying the attribute macros for +/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post). +/// +/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler). +/// +/// # Examples +/// ``` +/// # use actix_web::HttpResponse; +/// # use actix_web_codegen::routes; +/// #[routes] +/// #[get("/test")] +/// #[get("/test2")] +/// #[delete("/test")] +/// async fn example() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// ``` +#[proc_macro_attribute] +pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream { + route::with_methods(input) +} + macro_rules! method_macro { ($variant:ident, $method:ident) => { #[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index cae3cbd55..7a0658468 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -3,24 +3,12 @@ use std::{collections::HashSet, convert::TryFrom}; use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta, Path}; - -enum ResourceType { - Async, - Sync, -} - -impl ToTokens for ResourceType { - fn to_tokens(&self, stream: &mut TokenStream2) { - let ident = format_ident!("to"); - stream.append(ident); - } -} +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, Meta, NestedMeta, Path}; macro_rules! method_type { ( - $($variant:ident, $upper:ident,)+ + $($variant:ident, $upper:ident, $lower:ident,)+ ) => { #[derive(Debug, PartialEq, Eq, Hash)] pub enum MethodType { @@ -42,20 +30,27 @@ macro_rules! method_type { _ => Err(format!("Unexpected HTTP method: `{}`", method)), } } + + fn from_path(method: &Path) -> Result { + match () { + $(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+ + _ => Err(()), + } + } } }; } method_type! { - Get, GET, - Post, POST, - Put, PUT, - Delete, DELETE, - Head, HEAD, - Connect, CONNECT, - Options, OPTIONS, - Trace, TRACE, - Patch, PATCH, + Get, GET, get, + Post, POST, post, + Put, PUT, put, + Delete, DELETE, delete, + Head, HEAD, head, + Connect, CONNECT, connect, + Options, OPTIONS, options, + Trace, TRACE, trace, + Patch, PATCH, patch, } impl ToTokens for MethodType { @@ -90,6 +85,18 @@ impl Args { let mut wrappers = Vec::new(); let mut methods = HashSet::new(); + if args.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + format!( + r#"invalid service definition, expected #[{}("")]"#, + method + .map_or("route", |it| it.as_str()) + .to_ascii_lowercase() + ), + )); + } + let is_route_macro = method.is_none(); if let Some(method) = method { methods.insert(method); @@ -183,55 +190,27 @@ impl Args { } pub struct Route { + /// Name of the handler function being annotated. name: syn::Ident, - args: Args, + + /// Args passed to routing macro. + /// + /// When using `#[routes]`, this will contain args for each specific routing macro. + args: Vec, + + /// AST of the handler function being annotated. ast: syn::ItemFn, - resource_type: ResourceType, /// The doc comment attributes to copy to generated struct, if any. doc_attributes: Vec, } -fn guess_resource_type(typ: &syn::Type) -> ResourceType { - let mut guess = ResourceType::Sync; - - if let syn::Type::ImplTrait(typ) = typ { - for bound in typ.bounds.iter() { - if let syn::TypeParamBound::Trait(bound) = bound { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; - } - } - } - } - } - - guess -} - impl Route { pub fn new( args: AttributeArgs, ast: syn::ItemFn, method: Option, ) -> syn::Result { - if args.is_empty() { - return Err(syn::Error::new( - Span::call_site(), - format!( - r#"invalid service definition, expected #[{}("")]"#, - method - .map_or("route", |it| it.as_str()) - .to_ascii_lowercase() - ), - )); - } - let name = ast.sig.ident.clone(); // Try and pull out the doc comments so that we can reapply them to the generated struct. @@ -244,6 +223,7 @@ impl Route { .collect(); let args = Args::new(args, method)?; + if args.methods.is_empty() { return Err(syn::Error::new( Span::call_site(), @@ -251,25 +231,44 @@ impl Route { )); } - let resource_type = if ast.sig.asyncness.is_some() { - ResourceType::Async - } else { - match ast.sig.output { - syn::ReturnType::Default => { - return Err(syn::Error::new_spanned( - ast, - "Function has no return type. Cannot be used as handler", - )); - } - syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), - } - }; + if matches!(ast.sig.output, syn::ReturnType::Default) { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } + + Ok(Self { + name, + args: vec![args], + ast, + doc_attributes, + }) + } + + fn multiple(args: Vec, ast: syn::ItemFn) -> syn::Result { + let name = ast.sig.ident.clone(); + + // Try and pull out the doc comments so that we can reapply them to the generated struct. + // Note that multi line doc comments are converted to multiple doc attributes. + let doc_attributes = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .cloned() + .collect(); + + if matches!(ast.sig.output, syn::ReturnType::Default) { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } Ok(Self { name, args, ast, - resource_type, doc_attributes, }) } @@ -280,38 +279,57 @@ impl ToTokens for Route { let Self { name, ast, - args: - Args { + args, + doc_attributes, + } = self; + + let registrations: TokenStream2 = args + .iter() + .map(|args| { + let Args { path, resource_name, guards, wrappers, methods, - }, - resource_type, - doc_attributes, - } = self; - let resource_name = resource_name - .as_ref() - .map_or_else(|| name.to_string(), LitStr::value); - let method_guards = { - let mut others = methods.iter(); - // unwrapping since length is checked to be at least one - let first = others.next().unwrap(); + } = args; + + let resource_name = resource_name + .as_ref() + .map_or_else(|| name.to_string(), LitStr::value); + + let method_guards = { + let mut others = methods.iter(); + + // unwrapping since length is checked to be at least one + let first = others.next().unwrap(); + + if methods.len() > 1 { + quote! { + .guard( + ::actix_web::guard::Any(::actix_web::guard::#first()) + #(.or(::actix_web::guard::#others()))* + ) + } + } else { + quote! { + .guard(::actix_web::guard::#first()) + } + } + }; - if methods.len() > 1 { quote! { - .guard( - ::actix_web::guard::Any(::actix_web::guard::#first()) - #(.or(::actix_web::guard::#others()))* - ) + let __resource = ::actix_web::Resource::new(#path) + .name(#resource_name) + #method_guards + #(.guard(::actix_web::guard::fn_guard(#guards)))* + #(.wrap(#wrappers))* + .to(#name); + + ::actix_web::dev::HttpServiceFactory::register(__resource, __config); } - } else { - quote! { - .guard(::actix_web::guard::#first()) - } - } - }; + }) + .collect(); let stream = quote! { #(#doc_attributes)* @@ -321,14 +339,7 @@ impl ToTokens for Route { impl ::actix_web::dev::HttpServiceFactory for #name { fn register(self, __config: &mut actix_web::dev::AppService) { #ast - let __resource = ::actix_web::Resource::new(#path) - .name(#resource_name) - #method_guards - #(.guard(::actix_web::guard::fn_guard(#guards)))* - #(.wrap(#wrappers))* - .#resource_type(#name); - - ::actix_web::dev::HttpServiceFactory::register(__resource, __config) + #registrations } } }; @@ -357,6 +368,57 @@ pub(crate) fn with_method( } } +pub(crate) fn with_methods(input: TokenStream) -> TokenStream { + let mut ast = match syn::parse::(input.clone()) { + Ok(ast) => ast, + // on parse error, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(input, err), + }; + + let (methods, others) = ast + .attrs + .into_iter() + .map(|attr| match MethodType::from_path(&attr.path) { + Ok(method) => Ok((method, attr)), + Err(_) => Err(attr), + }) + .partition::, _>(Result::is_ok); + + ast.attrs = others.into_iter().map(Result::unwrap_err).collect(); + + let methods = + match methods + .into_iter() + .map(Result::unwrap) + .map(|(method, attr)| { + attr.parse_meta().and_then(|args| { + if let Meta::List(args) = args { + Args::new(args.nested.into_iter().collect(), Some(method)) + } else { + Err(syn::Error::new_spanned(attr, "Invalid input for macro")) + } + }) + }) + .collect::, _>>() + { + Ok(methods) if methods.is_empty() => return input_and_compile_error( + input, + syn::Error::new( + Span::call_site(), + "The #[routes] macro requires at least one `#[(..)]` attribute.", + ), + ), + Ok(methods) => methods, + Err(err) => return input_and_compile_error(input, err), + }; + + match Route::multiple(methods, ast) { + Ok(route) => route.into_token_stream().into(), + // on macro related error, make IDEs happy; see fn docs + Err(err) => input_and_compile_error(input, err), + } +} + /// Converts the error to a token stream and appends it to the original input. /// /// Returning the original input in addition to the error is good for IDEs which can gracefully diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 55c2417b2..10e906967 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -8,9 +8,11 @@ use actix_web::{ header::{HeaderName, HeaderValue}, StatusCode, }, - web, App, Error, HttpResponse, Responder, + web, App, Error, HttpRequest, HttpResponse, Responder, +}; +use actix_web_codegen::{ + connect, delete, get, head, options, patch, post, put, route, routes, trace, }; -use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_core::future::LocalBoxFuture; // Make sure that we can name function as 'config' @@ -89,8 +91,41 @@ async fn route_test() -> impl Responder { HttpResponse::Ok() } +#[routes] +#[get("/routes/test")] +#[get("/routes/test2")] +#[post("/routes/test")] +async fn routes_test() -> impl Responder { + HttpResponse::Ok() +} + +// routes overlap with the more specific route first, therefore accessible +#[routes] +#[get("/routes/overlap/test")] +#[get("/routes/overlap/{foo}")] +async fn routes_overlapping_test(req: HttpRequest) -> impl Responder { + // foo is only populated when route is not /routes/overlap/test + match req.match_info().get("foo") { + None => assert!(req.uri() == "/routes/overlap/test"), + Some(_) => assert!(req.uri() != "/routes/overlap/test"), + } + + HttpResponse::Ok() +} + +// routes overlap with the more specific route last, therefore inaccessible +#[routes] +#[get("/routes/overlap2/{foo}")] +#[get("/routes/overlap2/test")] +async fn routes_overlapping_inaccessible_test(req: HttpRequest) -> impl Responder { + // foo is always populated even when path is /routes/overlap2/test + assert!(req.match_info().get("foo").is_some()); + + HttpResponse::Ok() +} + #[get("/custom_resource_name", name = "custom")] -async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder { +async fn custom_resource_name_test<'a>(req: HttpRequest) -> impl Responder { assert!(req.url_for_static("custom").is_ok()); assert!(req.url_for_static("custom_resource_name_test").is_err()); HttpResponse::Ok() @@ -201,6 +236,9 @@ async fn test_body() { .service(patch_test) .service(test_handler) .service(route_test) + .service(routes_overlapping_test) + .service(routes_overlapping_inaccessible_test) + .service(routes_test) .service(custom_resource_name_test) .service(guard_test) }); @@ -258,6 +296,38 @@ async fn test_body() { let response = request.send().await.unwrap(); assert!(!response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/routes/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/test2")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::POST, srv.url("/routes/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/not-set")); + let response = request.send().await.unwrap(); + assert!(response.status().is_client_error()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap/bar")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/bar")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 976b3d52c..1f7996fd0 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -12,6 +12,10 @@ fn compile_macros() { t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); + t.pass("tests/trybuild/routes-ok.rs"); + t.compile_fail("tests/trybuild/routes-missing-method-fail.rs"); + t.compile_fail("tests/trybuild/routes-missing-args-fail.rs"); + t.pass("tests/trybuild/docstring-ok.rs"); t.pass("tests/trybuild/test-runtime.rs"); diff --git a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs new file mode 100644 index 000000000..65573cf79 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs @@ -0,0 +1,14 @@ +use actix_web_codegen::*; + +#[routes] +#[get] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); +} diff --git a/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr new file mode 100644 index 000000000..8efe0682b --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr @@ -0,0 +1,21 @@ +error: invalid service definition, expected #[get("")] + --> tests/trybuild/routes-missing-args-fail.rs:4:1 + | +4 | #[get] + | ^^^^^^ + | + = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: Invalid input for macro + --> tests/trybuild/routes-missing-args-fail.rs:4:1 + | +4 | #[get] + | ^^^^^^ + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-args-fail.rs:13:55 + | +13 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs new file mode 100644 index 000000000..f0271ef44 --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs @@ -0,0 +1,13 @@ +use actix_web_codegen::*; + +#[routes] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); +} diff --git a/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr new file mode 100644 index 000000000..b3795d74a --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr @@ -0,0 +1,15 @@ +error: The #[routes] macro requires at least one `#[(..)]` attribute. + --> tests/trybuild/routes-missing-method-fail.rs:3:1 + | +3 | #[routes] + | ^^^^^^^^^ + | + = note: this error originates in the attribute macro `routes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/routes-missing-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` + | | + | required by a bound introduced by this call diff --git a/actix-web-codegen/tests/trybuild/routes-ok.rs b/actix-web-codegen/tests/trybuild/routes-ok.rs new file mode 100644 index 000000000..98b5e553e --- /dev/null +++ b/actix-web-codegen/tests/trybuild/routes-ok.rs @@ -0,0 +1,23 @@ +use actix_web_codegen::*; + +#[routes] +#[get("/")] +#[post("/")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.post("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 0144cb912..f38282b41 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,12 +2,14 @@ ## Unreleased - 2022-xx-xx ### Added +- Add `#[routes]` macro to support multiple paths for one handler. [#2718] - Add `ServiceRequest::{parts, request}()` getter methods. [#2786] - Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752] ### Changed - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2718]: https://github.com/actix/actix-web/pull/2718 [#2752]: https://github.com/actix/actix-web/pull/2752 [#2786]: https://github.com/actix/actix-web/pull/2786 diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 4eab24cec..8d9e2dbcd 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -132,6 +132,7 @@ macro_rules! codegen_reexport { codegen_reexport!(main); codegen_reexport!(test); codegen_reexport!(route); +codegen_reexport!(routes); codegen_reexport!(head); codegen_reexport!(get); codegen_reexport!(post); From 9b0fdca6e9a0c7944a8063408a2acb85c598e857 Mon Sep 17 00:00:00 2001 From: Expyron <5100376+Expyron@users.noreply.github.com> Date: Fri, 22 Jul 2022 21:18:38 +0200 Subject: [PATCH 480/861] Remove some unnecessary uses of `once_cell::sync::Lazy` (#2816) --- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 4 ++-- actix-web/src/info.rs | 16 +++++--------- actix-web/src/middleware/compress.rs | 33 +++++++++++----------------- awc/Cargo.toml | 2 +- 6 files changed, 24 insertions(+), 35 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6f7563ffa..7adce434b 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -40,7 +40,7 @@ awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" futures-core = { version = "0.3.7", default-features = false } -http = "0.2.5" +http = "0.2.8" log = "0.4" socket2 = "0.4" serde = "1.0" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 03767ca4e..2948c4047 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,7 +68,7 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -http = "0.2.5" +http = "0.2.8" httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index ceb5b14dc..f43862494 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,14 +21,14 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -http = { version = "0.2.3", optional = true } +http = { version = "0.2.8", optional = true } regex = "1.5" serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -http = "0.2.5" +http = "0.2.8" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs index 77b98110e..7c685406e 100644 --- a/actix-web/src/info.rs +++ b/actix-web/src/info.rs @@ -2,7 +2,6 @@ use std::{convert::Infallible, net::SocketAddr}; use actix_utils::future::{err, ok, Ready}; use derive_more::{Display, Error}; -use once_cell::sync::Lazy; use crate::{ dev::{AppConfig, Payload, RequestHead}, @@ -13,12 +12,9 @@ use crate::{ FromRequest, HttpRequest, ResponseError, }; -static X_FORWARDED_FOR: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-for")); -static X_FORWARDED_HOST: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-host")); -static X_FORWARDED_PROTO: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-proto")); +static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); +static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host"); +static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto"); /// Trim whitespace then any quote marks. fn unquote(val: &str) -> &str { @@ -117,21 +113,21 @@ impl ConnectionInfo { } let scheme = scheme - .or_else(|| first_header_value(req, &*X_FORWARDED_PROTO)) + .or_else(|| first_header_value(req, &X_FORWARDED_PROTO)) .or_else(|| req.uri.scheme().map(Scheme::as_str)) .or_else(|| Some("https").filter(|_| cfg.secure())) .unwrap_or("http") .to_owned(); let host = host - .or_else(|| first_header_value(req, &*X_FORWARDED_HOST)) + .or_else(|| first_header_value(req, &X_FORWARDED_HOST)) .or_else(|| req.headers.get(&header::HOST)?.to_str().ok()) .or_else(|| req.uri.authority().map(Authority::as_str)) .unwrap_or_else(|| cfg.host()) .to_owned(); let realip_remote_addr = realip_remote_addr - .or_else(|| first_header_value(req, &*X_FORWARDED_FOR)) + .or_else(|| first_header_value(req, &X_FORWARDED_FOR)) .map(str::to_owned); let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string()); diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index ed4291bed..203e41ab2 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -220,32 +220,25 @@ static SUPPORTED_ENCODINGS_STRING: Lazy = Lazy::new(|| { encoding.join(", ") }); -static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { - let mut encodings = vec![Encoding::identity()]; - +static SUPPORTED_ENCODINGS: &[Encoding] = &[ + Encoding::identity(), #[cfg(feature = "compress-brotli")] { - encodings.push(Encoding::brotli()); - } - + Encoding::brotli() + }, #[cfg(feature = "compress-gzip")] { - encodings.push(Encoding::gzip()); - encodings.push(Encoding::deflate()); - } - + Encoding::gzip() + }, + #[cfg(feature = "compress-gzip")] + { + Encoding::deflate() + }, #[cfg(feature = "compress-zstd")] { - encodings.push(Encoding::zstd()); - } - - assert!( - !encodings.is_empty(), - "encodings can not be empty unless __compress feature has been explicitly enabled by itself" - ); - - encodings -}); + Encoding::zstd() + }, +]; // move cfg(feature) to prevents_double_compressing if more tests are added #[cfg(feature = "compress-gzip")] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9da103cb0..eb9310e26 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" -http = "0.2.5" +http = "0.2.8" itoa = "1" log =" 0.4" mime = "0.3" From 16c7c16463c62a7e1bb6cefe3f2d9613ab79fa97 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 22 Jul 2022 20:19:02 +0100 Subject: [PATCH 481/861] reduce scope of once_cell change --- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 4 ++-- actix-web/src/http/header/content_disposition.rs | 3 ++- awc/Cargo.toml | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 7adce434b..6f7563ffa 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -40,7 +40,7 @@ awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" futures-core = { version = "0.3.7", default-features = false } -http = "0.2.8" +http = "0.2.5" log = "0.4" socket2 = "0.4" serde = "1.0" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2948c4047..03767ca4e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,7 +68,7 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -http = "0.2.8" +http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index f43862494..141b2d39e 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,14 +21,14 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -http = { version = "0.2.8", optional = true } +http = { version = "0.2.5", optional = true } regex = "1.5" serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } -http = "0.2.8" +http = "0.2.5" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs index 8b7101aa1..edcfd632a 100644 --- a/actix-web/src/http/header/content_disposition.rs +++ b/actix-web/src/http/header/content_disposition.rs @@ -10,9 +10,10 @@ //! - Browser conformance tests at: //! - IANA assignment: +use std::fmt::{self, Write}; + use once_cell::sync::Lazy; use regex::Regex; -use std::fmt::{self, Write}; use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer}; use crate::http::header; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index eb9310e26..9da103cb0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -73,7 +73,7 @@ derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" -http = "0.2.8" +http = "0.2.5" itoa = "1" log =" 0.4" mime = "0.3" From 6485434a33073c1bd3fd913ffe666852914419d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 22 Jul 2022 20:19:15 +0100 Subject: [PATCH 482/861] update bump script --- actix-web/src/data.rs | 2 +- scripts/bump | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs index a689d13e0..89104a1ac 100644 --- a/actix-web/src/data.rs +++ b/actix-web/src/data.rs @@ -118,7 +118,7 @@ impl Deref for Data { impl Clone for Data { fn clone(&self) -> Data { - Data(self.0.clone()) + Data(Arc::clone(&self.0)) } } diff --git a/scripts/bump b/scripts/bump index 209e2281d..33ea52010 100755 --- a/scripts/bump +++ b/scripts/bump @@ -98,7 +98,7 @@ rm -f $README_FILE.bak echo "manifest, changelog, and readme updated" echo echo "check other references:" -rg --glob='**/Cargo.toml' "\ +rg --glob='**/{Cargo.toml,README.md}' "\ ${PACKAGE_NAME} ?= ?\"[^\"]+\"\ |${PACKAGE_NAME} ?=.*version ?= ?\"([^\"]+)\"\ |package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\"([^\"]+)\"\ From 14bcf72ec15bbfe1b1b582235f6b735538b8986b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 22 Jul 2022 20:21:58 +0100 Subject: [PATCH 483/861] web utilizes const header names --- actix-web/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index c58e1604b..c96f3c1bb 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -84,11 +84,12 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +http = "0.2.8" itoa = "1" language-tags = "0.3" -once_cell = "1.5" log = "0.4" mime = "0.3" +once_cell = "1.5" pin-project-lite = "0.2.7" regex = "1.5.5" serde = "1.0" From 8d260e599ffa3940413f46738b1489a6d0dd6a20 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Jul 2022 02:48:28 +0100 Subject: [PATCH 484/861] clippy --- actix-router/src/resource_path.rs | 2 +- actix-web/src/middleware/compress.rs | 1 + actix-web/src/request.rs | 2 +- actix-web/src/server.rs | 2 ++ actix-web/src/types/path.rs | 1 + actix-web/src/types/query.rs | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/actix-router/src/resource_path.rs b/actix-router/src/resource_path.rs index 00eb0927c..45948aa2a 100644 --- a/actix-router/src/resource_path.rs +++ b/actix-router/src/resource_path.rs @@ -27,7 +27,7 @@ impl<'a> ResourcePath for &'a str { impl ResourcePath for bytestring::ByteString { fn path(&self) -> &str { - &*self + self } } diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index 203e41ab2..51b44c6ef 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -319,6 +319,7 @@ mod tests { .to_request(); let res = test::call_service(&app, req).await; assert_eq!(res.status(), StatusCode::OK); + #[allow(clippy::mutable_key_type)] let vary_headers = res.headers().get_all(header::VARY).collect::>(); assert!(vary_headers.contains(&HeaderValue::from_static("x-test"))); assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding"))); diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index d26488e2b..2998c7010 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -253,7 +253,7 @@ impl HttpRequest { #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { if !self.extensions().contains::() { - let info = ConnectionInfo::new(self.head(), &*self.app_config()); + let info = ConnectionInfo::new(self.head(), self.app_config()); self.extensions_mut().insert(info); } diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 169eafab0..9c5076d36 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -401,6 +401,8 @@ where .into_factory() .map_err(|err| err.into().error_response()); + // false positive lint (?) + #[allow(clippy::significant_drop_in_scrutinee)] let acceptor_config = match c.tls_handshake_timeout { Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), None => TlsAcceptorConfig::default(), diff --git a/actix-web/src/types/path.rs b/actix-web/src/types/path.rs index 34c335ba9..a90c912f6 100644 --- a/actix-web/src/types/path.rs +++ b/actix-web/src/types/path.rs @@ -134,6 +134,7 @@ where /// ``` #[derive(Clone, Default)] pub struct PathConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, } diff --git a/actix-web/src/types/query.rs b/actix-web/src/types/query.rs index 97d17123d..e71b886f2 100644 --- a/actix-web/src/types/query.rs +++ b/actix-web/src/types/query.rs @@ -169,6 +169,7 @@ impl FromRequest for Query { /// ``` #[derive(Clone, Default)] pub struct QueryConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, } From 6408291ab00ed4244ceaa4081315691156d66011 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Jul 2022 17:26:48 +0200 Subject: [PATCH 485/861] appease clippy by deriving Eq on a bunch of items (#2818) --- actix-files/src/error.rs | 4 ++-- actix-http/src/error.rs | 2 +- actix-http/src/h1/chunked.rs | 2 +- actix-http/src/h1/decoder.rs | 6 +++--- actix-http/src/h1/payload.rs | 2 +- actix-http/src/header/shared/extended.rs | 2 +- actix-http/src/header/shared/quality_item.rs | 2 +- actix-http/src/message.rs | 2 +- actix-http/src/ws/codec.rs | 6 +++--- actix-http/src/ws/mod.rs | 2 +- actix-router/src/router.rs | 2 +- actix-web/src/error/mod.rs | 2 +- actix-web/src/http/header/content_disposition.rs | 6 +++--- actix-web/src/http/header/if_range.rs | 2 +- actix-web/src/http/header/macros.rs | 3 ++- actix-web/src/http/header/range.rs | 2 +- actix-web/src/types/either.rs | 2 +- 17 files changed, 25 insertions(+), 24 deletions(-) diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 6682529f8..2f3a36cd1 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -2,7 +2,7 @@ use actix_web::{http::StatusCode, ResponseError}; use derive_more::Display; /// Errors which can occur when serving static files. -#[derive(Display, Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Display)] pub enum FilesError { /// Path is not a directory #[allow(dead_code)] @@ -22,7 +22,7 @@ impl ResponseError for FilesError { } #[allow(clippy::enum_variant_names)] -#[derive(Display, Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Display)] #[non_exhaustive] pub enum UriSegmentError { /// The segment started with the wrapped invalid character. diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 3fce0a60b..41522a254 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -388,7 +388,7 @@ impl StdError for DispatchError { /// A set of error that can occur during parsing content type. #[derive(Debug, Display, Error)] -#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(test, derive(PartialEq, Eq))] #[non_exhaustive] pub enum ContentTypeError { /// Can not parse content type diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index c1dd4b283..4005ed892 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -15,7 +15,7 @@ macro_rules! byte ( }) ); -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(super) enum ChunkedState { Size, SizeLws, diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 203b6c531..0b06bfe24 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -440,7 +440,7 @@ impl HeaderIndex { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Chunk type yielded while decoding a payload. pub enum PayloadItem { Chunk(Bytes), @@ -450,7 +450,7 @@ pub enum PayloadItem { /// Decoder that can handle different payload types. /// /// If a message body does not use `Transfer-Encoding`, it should include a `Content-Length`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PayloadDecoder { kind: Kind, } @@ -476,7 +476,7 @@ impl PayloadDecoder { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] enum Kind { /// A reader used when a `Content-Length` header is passed with a positive integer. Length(u64), diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index a8c632396..d6ffa662e 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -16,7 +16,7 @@ use crate::error::PayloadError; /// max buffer size 32k pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum PayloadStatus { Read, Pause, diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 1af9ca20e..cd8adb2bd 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -12,7 +12,7 @@ use crate::header::{Charset, HTTP_VALUE}; /// - A character sequence representing the actual value (`value`), separated by single quotes. /// /// It is defined in [RFC 5987 §3.2](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ExtendedValue { /// The character set that is used to encode the `value` to a string. pub charset: Charset, diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 71a3bdd53..0b35b5401 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -147,7 +147,7 @@ mod tests { // copy of encoding from actix-web headers #[allow(clippy::enum_variant_names)] // allow Encoding prefix on EncodingExt - #[derive(Clone, PartialEq, Debug)] + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Encoding { Chunked, Brotli, diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 5616a4762..7469d74ee 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, ops, rc::Rc}; use bitflags::bitflags; /// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConnectionType { /// Close connection after response. Close, diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 3aa325d6a..4a2e741b6 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -11,7 +11,7 @@ use super::{ }; /// A WebSocket message. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Message { /// Text message. Text(ByteString), @@ -36,7 +36,7 @@ pub enum Message { } /// A WebSocket frame. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Frame { /// Text frame. Note that the codec does not validate UTF-8 encoding. Text(Bytes), @@ -58,7 +58,7 @@ pub enum Frame { } /// A WebSocket continuation item. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Item { FirstText(Bytes), FirstBinary(Bytes), diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 568d801a2..75d4ca628 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -67,7 +67,7 @@ pub enum ProtocolError { } /// WebSocket handshake errors -#[derive(Debug, Clone, Copy, PartialEq, Display, Error)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)] pub enum HandshakeError { /// Only get method is allowed. #[display(fmt = "Method not allowed.")] diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index 8ed966b59..064c5e904 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -1,6 +1,6 @@ use crate::{IntoPatterns, Resource, ResourceDef}; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ResourceId(pub u16); /// Resource router. diff --git a/actix-web/src/error/mod.rs b/actix-web/src/error/mod.rs index 6095cd5d2..604c539f3 100644 --- a/actix-web/src/error/mod.rs +++ b/actix-web/src/error/mod.rs @@ -42,7 +42,7 @@ pub struct BlockingError; impl ResponseError for crate::error::BlockingError {} /// Errors which can occur when attempting to generate resource uri. -#[derive(Debug, PartialEq, Display, Error, From)] +#[derive(Debug, PartialEq, Eq, Display, Error, From)] #[non_exhaustive] pub enum UrlGenerationError { /// Resource not found. diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs index edcfd632a..0bb459193 100644 --- a/actix-web/src/http/header/content_disposition.rs +++ b/actix-web/src/http/header/content_disposition.rs @@ -37,7 +37,7 @@ fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { } /// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DispositionType { /// Inline implies default processing. Inline, @@ -79,7 +79,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert!(param.is_filename()); /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from @@ -302,7 +302,7 @@ impl DispositionParam { /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. /// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ContentDisposition { /// The disposition type pub disposition: DispositionType, diff --git a/actix-web/src/http/header/if_range.rs b/actix-web/src/http/header/if_range.rs index b845fb3bf..eb3632a4d 100644 --- a/actix-web/src/http/header/if_range.rs +++ b/actix-web/src/http/header/if_range.rs @@ -57,7 +57,7 @@ use crate::HttpMessage; /// IfRange::Date(fetched.into()) /// ); /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum IfRange { /// The entity-tag the client has of the resource. EntityTag(EntityTag), diff --git a/actix-web/src/http/header/macros.rs b/actix-web/src/http/header/macros.rs index 25f40a52b..b40eca03b 100644 --- a/actix-web/src/http/header/macros.rs +++ b/actix-web/src/http/header/macros.rs @@ -224,10 +224,11 @@ macro_rules! common_header { // List header, one or more items with "*" option ($(#[$attrs:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { $(#[$attrs])* - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Debug, PartialEq, Eq)] pub enum $id { /// Any value is a match Any, + /// Only the listed items are a match Items(Vec<$item>), } diff --git a/actix-web/src/http/header/range.rs b/actix-web/src/http/header/range.rs index 68028f53a..2326bb19c 100644 --- a/actix-web/src/http/header/range.rs +++ b/actix-web/src/http/header/range.rs @@ -53,7 +53,7 @@ use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderVa /// builder.insert_header(Range::bytes(1, 100)); /// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)])); /// ``` -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Range { /// Byte range. Bytes(Vec), diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs index c0faf04b1..119dd0d62 100644 --- a/actix-web/src/types/either.rs +++ b/actix-web/src/types/either.rs @@ -73,7 +73,7 @@ use crate::{ /// } /// } /// ``` -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Either { /// A value of type `L`. Left(L), From 20f4cfe6b58cd409252de906944352ea80f1dab6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Jul 2022 17:27:01 +0200 Subject: [PATCH 486/861] fix partial ranges for video content (#2817) fixes #2815 --- actix-files/CHANGES.md | 3 +++ actix-files/src/named.rs | 25 ++++++++++++++++++++----- actix-files/tests/encoding.rs | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index b127cd9ea..dea26269b 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2022-xx-xx +- Allow partial range responses for video content to start streaming sooner. [#2817] - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2817]: https://github.com/actix/actix-web/pull/2817 + ## 0.6.1 - 2022-06-11 - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 5580e6f7e..1213534c2 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -528,11 +528,26 @@ impl NamedFile { length = ranges[0].length; offset = ranges[0].start; - // don't allow compression middleware to modify partial content - res.insert_header(( - header::CONTENT_ENCODING, - HeaderValue::from_static("identity"), - )); + // When a Content-Encoding header is present in a 206 partial content response + // for video content, it prevents browser video players from starting playback + // before loading the whole video and also prevents seeking. + // + // See: https://github.com/actix/actix-web/issues/2815 + // + // The assumption of this fix is that the video player knows to not send an + // Accept-Encoding header for this request and that downstream middleware will + // not attempt compression for requests without it. + // + // TODO: Solve question around what to do if self.encoding is set and partial + // range is requested. Reject request? Ignoring self.encoding seems wrong, too. + // In practice, it should not come up. + if req.headers().contains_key(&header::ACCEPT_ENCODING) { + // don't allow compression middleware to modify partial content + res.insert_header(( + header::CONTENT_ENCODING, + HeaderValue::from_static("identity"), + )); + } res.insert_header(( header::CONTENT_RANGE, diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index 080292af5..7aec25ff9 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -1,11 +1,11 @@ -use actix_files::Files; +use actix_files::{Files, NamedFile}; use actix_web::{ http::{ header::{self, HeaderValue}, StatusCode, }, test::{self, TestRequest}, - App, + web, App, }; #[actix_web::test] @@ -36,3 +36,31 @@ async fn test_utf8_file_contents() { Some(&HeaderValue::from_static("text/plain")), ); } + +#[actix_web::test] +async fn partial_range_response_encoding() { + let srv = test::init_service(App::new().default_service(web::to(|| async { + NamedFile::open_async("./tests/test.binary").await.unwrap() + }))) + .await; + + // range request without accept-encoding returns no content-encoding header + let req = TestRequest::with_uri("/") + .append_header((header::RANGE, "bytes=10-20")) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); + assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + + // range request with accept-encoding returns a content-encoding header + let req = TestRequest::with_uri("/") + .append_header((header::RANGE, "bytes=10-20")) + .append_header((header::ACCEPT_ENCODING, "identity")) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); + assert_eq!( + res.headers().get(header::CONTENT_ENCODING).unwrap(), + "identity" + ); +} From 3e25742a41f3e51c96afbb3cafc3042f67d62736 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Jul 2022 16:37:59 +0100 Subject: [PATCH 487/861] prepare actix-files release 0.6.2 --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 2 +- actix-files/README.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index dea26269b..5c0a48024 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 0.6.2 - 2022-07-23 - Allow partial range responses for video content to start streaming sooner. [#2817] - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 02543095f..5559d1b7e 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.1" +version = "0.6.2" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", diff --git a/actix-files/README.md b/actix-files/README.md index 35db41c9a..c3204a68c 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.2)](https://docs.rs/actix-files/0.6.2) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) +[![dependency status](https://deps.rs/crate/actix-files/0.6.2/status.svg)](https://deps.rs/crate/actix-files/0.6.2) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From ce6d52021538430ba4295cd7257f7ed076895bdb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 24 Jul 2022 02:11:21 +0100 Subject: [PATCH 488/861] prepare actix-http-test release 3.0.0 --- actix-http-test/CHANGES.md | 18 +++++++++++++++++- actix-http-test/Cargo.toml | 2 +- actix-http-test/README.md | 4 ++-- actix-http/Cargo.toml | 2 +- actix-test/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index f91ef4081..9aad2e4ba 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,9 +1,24 @@ # Changes ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +## 3.0.0 - 2022-07-24 +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Added `TestServer::client_headers` method. [#2097] +- Update `actix-server` dependency to `2`. +- Update `actix-tls` dependency to `3`. +- Update `bytes` to `1.0`. [#1813] +- Minimum supported Rust version (MSRV) is now 1.57. + +[#2442]: https://github.com/actix/actix-web/pull/2442 +[#2097]: https://github.com/actix/actix-web/pull/2097 +[#1813]: https://github.com/actix/actix-web/pull/1813 + + +